繁体   English   中英

如何使用haskell parsec库解析带有applicative仿函数的Intel Hex Record?

[英]How do you parse an Intel Hex Record with applicative functors using the haskell parsec library?

我想用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

谁能帮助我? 谢谢。

为了使用parsec,您的问题中没有包含许多内容。 要定义像startOfRecord这样的东西,我们需要禁用可怕的单态限制。 如果我们想为startOfRecord类的东西编写类型签名,我们还需要启用FlexibleContexts 我们还需要导入parsec, Control.ApplicativeNumeric (readHex)

{-# LANGUAGE NoMonomorphismRestriction #-}
{-# LANGUAGE FlexibleContexts #-}

import Text.Parsec
import Control.Applicative
import Numeric (readHex)

我还打算使用Word8Word16Data.Word因为严丝合缝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风格中的Applicative被写为<*>.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 ,一般来说,根据内容选择结构是没有办法的。 这是ApplicativeMonad之间的巨大差异; Monad的绑定, (>>=) :: forall a b. ma -> (a -> mb) -> mb (>>=) :: forall a b. ma -> (a -> mb) -> mb ,允许您根据内容选择结构。 这正是我们需要做的recordData根据我们之前通过读取byteCount获得的结果来确定如何读取byteCount

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相比)的局限性在于你只能解析无上下文的表达式。

我的意思是,关于如何在某一点解析的决定不能依赖于之前解析的 ,只取决于结构(即解析器失败,因此我们尝试应用不同的值)。

我发现这可以从运营商自己解释:

(<*>) :: Applicative f => f (a -> b) -> f a -> f b
(>>=) :: Monad m => m a -> (a -> m b) -> m b

对于<*>您可以看到所有内容都发生在'Applicative'中包含的值的级别,而对于>>=该值可用于影响包含结构。 这正是使Monads比Applicative更强大的原因。

对于你的问题,这意味着你需要使用monadic解析器将所有单个部分粘在一起,如下所示:

parseRecord = do
  count <- byteCount
  ...
  rData <- recordData count
  ...
  return (count,rData,...)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM