[英]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.Applicative
和Numeric (readHex)
{-# LANGUAGE NoMonomorphismRestriction #-}
{-# LANGUAGE FlexibleContexts #-}
import Text.Parsec
import Control.Applicative
import Numeric (readHex)
我還打算使用Word8
和Word16
從Data.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
,一般來說,根據內容選擇結構是沒有辦法的。 這是Applicative
和Monad
之間的巨大差異; 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.