簡體   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