2009-05-04 89 views
1

這是一個相當長的問題,所以請和我一起裸照。你會如何解決這個數據解析問題?

我們正在爲一塊硬件實現一個模擬器,該硬件同時正在開發中。這個想法是給第三方 一個軟件解決方案來測試他們的客戶端軟件,並給開發人員一個參考點來實現他們的固件。

編寫硬件協議的人員使用名爲INCA_XDR的定製的SUN XDR的 版本。這是一個序列化和反序列化消息的工具。它用C語言編寫,我們希望避免任何 本地代碼,因此我們正在手動解析協議數據。

該協議是由性質相當複雜和數據分組 可以具有許多不同的結構,但它總是具有相同的全局結構:

[HEAD] [INTRO] [DATA] [TAIL]

[HEAD] = 
    byte sync 0x03 
    byte length X  [MSB]  X = length of [HEADER] + [INTRO] + [DATA] 
    byte length X  [LSB]  X = length of [HEADER] + [INTRO] + [DATA] 
    byte check X  [MSB]  X = crc of [INTRO] [DATA] 
    byte check X  [LSB]  X = crc of [INTRO] [DATA] 
    byte headercheck X    X = XOR over [SYNC] [LENGTH] [CHECK] 

[INTRO] 
    byte version 0x03 
    byte address X     X = 0 for point-to-point, 1-254 for specific controller, 255 = broadcast 
    byte sequence X     X = sequence number 
    byte group X  [MSB]  X = The category of the message 
    byte group X  [LSB]  X = The category of the message 
    byte type X   [MSB]  X = The id of the message 
    byte type X   [LSB]  X = The id of the message 

[DATA] = 
    The actuall data for the specified message, 
    this format really differs a lot. 

    It always starts with a DRCode which is one byte. 
    It more or less specifies the general structure of 
    the data, but even within the same structure the data 
    can mean many different things and have different lenghts. 
    (I think this is an artifact of the INCA_XDR tool) 

[TAIL] = 
    byte 0x0D 

正如你可以看到有大量的開銷數據,但這是因爲 協議需要與兩個RS232(點對點,點對多點)和TCP/IP(P2P)的工作。

name  size value 
    drcode  1  1 
    name  8    contains a name that can be used as a file name (only alphanumeric characters allowed) 
    timestamp 14    yyyymmddhhmmss contains timestamp of bitmap library 
    size  4    size of bitmap library to be loaded 
    options  1    currently no options 

或者,它可能有一個完全不同的結構:

name  size value 
    drcode  1  2 
    lastblock 1  0 - 1 1 indicates last block. Firmware can be stored 
    blocknumber 2    Indicates block of firmware 
    blocksize 2  N  size of block to load 
    blockdata N    data of block of firmware 

有時,它只是一個DRCode並沒有額外的數據。

基於組和類型字段,模擬器 需要執行某些操作。所以首先我們看看這兩個字段,並基於此我們知道對數據 有什麼期望,並且必須正確解析它。

然後需要生成響應數據,其中還有許多不同的數據結構。一些消息簡單地生成ACK或NACK消息,而另一些則生成數據的真實答覆。

我們決定把東西分成小塊。

首先是IDataProcessor。

實現此接口的類負責 驗證原始數據並生成Message類的實例。 他們不負責通信,他們只是通過一個字節[]

原始數據驗證意味着檢查標題的校驗和,crc和長度錯誤。

生成的消息被傳遞給實現IMessageProcessor的類。 即使原始數據被認爲是無效的,因爲IDataProcessor沒有 迴應消息的概念或其他任何東西,它所做的只是驗證原始數據。

,告知錯誤IMessageProcessor,一些額外的屬性已添加 到Message類:

bool nakError = false; 
bool tailError = false; 
bool crcError = false; 
bool headerError = false; 
bool lengthError = false; 

他們沒有相關的協議,只存在了IMessageProcessor

的IMessageProcessor是真正的工作在哪裏完成。 因爲所有不同的消息組和類型的我決定 使用F#實現IMessageProcessor接口,因爲模式匹配 似乎是一個很好的方式,以避免大量的嵌套if/else和種姓語句。我之前沒有使用過F#或LINQ和SQL以外的函數式語言的經驗)

IMessageProcessor分析數據並決定在IHardwareController上應該調用哪些方法 。這似乎是多餘的有IHardwareController, 但我們希望能夠有不同的實現 掉出來,而不是將被迫使用F#。目前的實現是一個WPF窗口,但它可能是一個Cocoa#窗口或簡單的控制檯。

IHardwareController還負責管理狀態,因爲 開發人員應該能夠通過用戶界面操縱硬件參數和錯誤。

因此,一旦IMessageProcessor在IHardwareController上調用了正確的方法, 就必須生成響應消息。再次...這些響應消息 中的數據可以有許多不同的結構。

最終IDataFactory用於將消息轉換爲原始協議數據 準備發送到任何負責通信的類。 (數據的附加封裝可能需要例如)

這是什麼「硬」關於編寫這些代碼,但所有的不同 命令和數據結構需要很多很多的代碼,並有少數 事情我們可以重用。 (至少據我現在可以看到的,希望有人能證明我錯了)

這是我第一次使用F#,所以我其實學習,我去。下面的代碼遠沒有完成 ,可能看起來像一個巨大的混亂。它只有實現了所有消息的漢福的協議 ,我可以告訴你有很多很多的人。所以這個文件會變得很大!

重要的是知道:字節順序顛倒通過線路(歷史原因)

module Arendee.Hardware.MessageProcessors 

open System; 
open System.Collections 
open Arendee.Hardware.Extenders 
open Arendee.Hardware.Interfaces 
open System.ComponentModel.Composition 
open System.Threading 
open System.Text 

let VPL_NOERROR = (uint16)0 
let VPL_CHECKSUM = (uint16)1 
let VPL_FRAMELENGTH = (uint16)2 
let VPL_OUTOFSEQUENCE = (uint16)3 
let VPL_GROUPNOTSUPPORTED = (uint16)4 
let VPL_REQUESTNOTSUPPORTED = (uint16)5 
let VPL_EXISTS = (uint16)6 
let VPL_INVALID = (uint16)7 
let VPL_TYPERROR = (uint16)8 
let VPL_NOTLOADING = (uint16)9 
let VPL_NOTFOUND = (uint16)10 
let VPL_OUTOFMEM = (uint16)11 
let VPL_INUSE = (uint16)12 
let VPL_SIZE = (uint16)13 
let VPL_BUSY = (uint16)14 
let SYNC_BYTE = (byte)0xE3 
let TAIL_BYTE = (byte)0x0D 
let MESSAGE_GROUP_VERSION = 3uy 
let MESSAGE_GROUP = 701us 


[<Export(typeof<IMessageProcessor>)>] 
type public StandardMessageProcessor() = class 
    let mutable controller : IHardwareController = null    

    interface IMessageProcessor with 
     member this.ProcessMessage m : Message = 
      printfn "%A" controller.Status 
      controller.Status <- ControllerStatusExtender.DisableBit(controller.Status,ControllerStatus.Nak) 

      match m with 
      | m when m.LengthError -> this.nakResponse(m,VPL_FRAMELENGTH) 
      | m when m.CrcError -> this.nakResponse(m,VPL_CHECKSUM) 
      | m when m.HeaderError -> this.nakResponse(m,VPL_CHECKSUM) 
      | m -> this.processValidMessage m 
      | _ -> null  

     member public x.HardwareController 
      with get() = controller 
      and set y = controller <- y     
    end 

    member private this.processValidMessage (m : Message) = 
     match m.Intro.MessageGroup with 
     | 701us -> this.processDefaultGroupMessage(m); 
     | _ -> this.nakResponse(m, VPL_GROUPNOTSUPPORTED); 

    member private this.processDefaultGroupMessage(m : Message) = 
     match m.Intro.MessageType with 
     | (1us) -> this.firmwareVersionListResponse(m)      //ListFirmwareVersions    0 
     | (2us) -> this.StartLoadingFirmwareVersion(m)      //StartLoadingFirmwareVersion  1 
     | (3us) -> this.LoadFirmwareVersionBlock(m)      //LoadFirmwareVersionBlock   2 
     | (4us) -> this.nakResponse(m, VPL_FRAMELENGTH)      //RemoveFirmwareVersion    3 
     | (5us) -> this.nakResponse(m, VPL_FRAMELENGTH)      //ActivateFirmwareVersion   3   
     | (12us) -> this.nakResponse(m,VPL_FRAMELENGTH)      //StartLoadingBitmapLibrary   2 
     | (13us) -> this.nakResponse(m,VPL_FRAMELENGTH)      //LoadBitmapLibraryBlock   2   
     | (21us) -> this.nakResponse(m, VPL_FRAMELENGTH)      //ListFonts       0 
     | (22us) -> this.nakResponse(m, VPL_FRAMELENGTH)      //LoadFont       4 
     | (23us) -> this.nakResponse(m, VPL_FRAMELENGTH)      //RemoveFont      3 
     | (24us) -> this.nakResponse(m, VPL_FRAMELENGTH)      //SetDefaultFont     3   
     | (31us) -> this.nakResponse(m, VPL_FRAMELENGTH)      //ListParameterSets     0 
     | (32us) -> this.nakResponse(m, VPL_FRAMELENGTH)      //LoadParameterSets     4 
     | (33us) -> this.nakResponse(m, VPL_FRAMELENGTH)      //RemoveParameterSet    3 
     | (34us) -> this.nakResponse(m, VPL_FRAMELENGTH)      //ActivateParameterSet    3 
     | (35us) -> this.nakResponse(m, VPL_FRAMELENGTH)      //GetParameterSet     3   
     | (41us) -> this.nakResponse(m, VPL_FRAMELENGTH)      //StartSelfTest      0 
     | (42us) -> this.returnStatus(m)          //GetStatus       0 
     | (43us) -> this.nakResponse(m, VPL_FRAMELENGTH)      //GetStatusDetail     0 
     | (44us) -> this.ResetStatus(m)      //ResetStatus      5 
     | (45us) -> this.nakResponse(m, VPL_FRAMELENGTH)      //SetDateTime      6 
     | (46us) -> this.nakResponse(m, VPL_FRAMELENGTH)      //GetDateTime      0 
     | _ -> this.nakResponse(m, VPL_REQUESTNOTSUPPORTED) 



    (* The various responses follow *) 

    //Generate a NAK response 
    member private this.nakResponse (message : Message , error) = 
     controller.Status <- controller.Status ||| ControllerStatus.Nak 
     let intro = new MessageIntro() 
     intro.MessageGroupVersion <- MESSAGE_GROUP_VERSION 
     intro.Address <- message.Intro.Address 
     intro.SequenceNumber <- this.setHigh(message.Intro.SequenceNumber) 
     intro.MessageGroup <- MESSAGE_GROUP 
     intro.MessageType <- 130us 
     let errorBytes = UShortExtender.ToIntelOrderedByteArray(error) 
     let data = Array.zero_create(5) 
     let x = this.getStatusBytes 
     let y = this.getStatusBytes 
     data.[0] <- 7uy 
     data.[1..2] <- this.getStatusBytes 
     data.[3..4] <- errorBytes  
     let header = this.buildHeader intro data 
     let message = new Message() 
     message.Header <- header 
     message.Intro <- intro 
     message.Tail <- TAIL_BYTE 
     message.Data <- data 
     message 

    //Generate an ACK response 
    member private this.ackResponse (message : Message) = 
     let intro = new MessageIntro() 
     intro.MessageGroupVersion <- MESSAGE_GROUP_VERSION 
     intro.Address <- message.Intro.Address 
     intro.SequenceNumber <- this.setHigh(message.Intro.SequenceNumber) 
     intro.MessageGroup <- MESSAGE_GROUP 
     intro.MessageType <- 129us 
     let data = Array.zero_create(3); 
     data.[0] <- 0x05uy 
     data.[1..2] <- this.getStatusBytes 
     let header = this.buildHeader intro data 
     message.Header <- header 
     message.Intro <- intro 
     message.Tail <- TAIL_BYTE 
     message.Data <- data 
     message   

    //Generate a ReturnFirmwareVersionList 
    member private this.firmwareVersionListResponse (message : Message) = 
     //Validation 
     if message.Data.[0] <> 0x00uy then 
      this.nakResponse(message,VPL_INVALID) 
     else 
      let intro = new MessageIntro() 
      intro.MessageGroupVersion <- MESSAGE_GROUP_VERSION 
      intro.Address <- message.Intro.Address 
      intro.SequenceNumber <- this.setHigh(message.Intro.SequenceNumber) 
      intro.MessageGroup <- MESSAGE_GROUP 
      intro.MessageType <- 132us  
      let firmwareVersions = controller.ReturnFirmwareVersionList(); 
      let firmwareVersionBytes = BitConverter.GetBytes((uint16)firmwareVersions.Count) |> Array.rev 

      //Create the data 
      let data = Array.zero_create(3 + (int)firmwareVersions.Count * 27) 
      data.[0] <- 0x09uy        //drcode 
      data.[1..2] <- firmwareVersionBytes    //Number of firmware versions 

      let mutable index = 0 
      let loops = firmwareVersions.Count - 1 
      for i = 0 to loops do 
       let nameBytes = ASCIIEncoding.ASCII.GetBytes(firmwareVersions.[i].Name) |> Array.rev 
       let timestampBytes = this.getTimeStampBytes firmwareVersions.[i].Timestamp |> Array.rev 
       let sizeBytes = BitConverter.GetBytes(firmwareVersions.[i].Size) |> Array.rev 

       data.[index + 3 .. index + 10] <- nameBytes 
       data.[index + 11 .. index + 24] <- timestampBytes 
       data.[index + 25 .. index + 28] <- sizeBytes 
       data.[index + 29] <- firmwareVersions.[i].Status 
       index <- index + 27    

      let header = this.buildHeader intro data 
      message.Header <- header 
      message.Intro <- intro 
      message.Data <- data 
      message.Tail <- TAIL_BYTE 
      message 

    //Generate ReturnStatus 
    member private this.returnStatus (message : Message) = 
     //Validation 
     if message.Data.[0] <> 0x00uy then 
      this.nakResponse(message,VPL_INVALID) 
     else 
      let intro = new MessageIntro() 
      intro.MessageGroupVersion <- MESSAGE_GROUP_VERSION 
      intro.Address <- message.Intro.Address 
      intro.SequenceNumber <- this.setHigh(message.Intro.SequenceNumber) 
      intro.MessageGroup <- MESSAGE_GROUP 
      intro.MessageType <- 131us 

      let statusDetails = controller.ReturnStatus(); 

      let sizeBytes = BitConverter.GetBytes((uint16)statusDetails.Length) |> Array.rev 

      let detailBytes = ASCIIEncoding.ASCII.GetBytes(statusDetails) |> Array.rev 

      let data = Array.zero_create(statusDetails.Length + 5) 
      data.[0] <- 0x08uy 
      data.[1..2] <- this.getStatusBytes 
      data.[3..4] <- sizeBytes //Details size 
      data.[5..5 + statusDetails.Length - 1] <- detailBytes 

      let header = this.buildHeader intro data 
      message.Header <- header 
      message.Intro <- intro 
      message.Data <- data 
      message.Tail <- TAIL_BYTE 
      message 

    //Reset some status bytes  
    member private this.ResetStatus (message : Message) = 
     if message.Data.[0] <> 0x05uy then 
      this.nakResponse(message, VPL_INVALID) 
     else   
      let flagBytes = message.Data.[1..2] |> Array.rev 
      let flags = Enum.ToObject(typeof<ControllerStatus>,BitConverter.ToInt16(flagBytes,0)) :?> ControllerStatus 
      let retVal = controller.ResetStatus flags 

      if retVal <> 0x00us then 
       this.nakResponse(message,retVal) 
      else 
       this.ackResponse(message) 

    //StartLoadingFirmwareVersion (Ack/Nak) 
    member private this.StartLoadingFirmwareVersion (message : Message) = 
     if (message.Data.[0] <> 0x01uy) then 
      this.nakResponse(message, VPL_INVALID) 
     else 
      //Analyze the data 
      let name = message.Data.[1..8] |> Array.rev |> ASCIIEncoding.ASCII.GetString 
      let text = message.Data.[9..22] |> Array.rev |> Seq.map(fun x -> ASCIIEncoding.ASCII.GetBytes(x.ToString()).[0]) |> Seq.to_array |> ASCIIEncoding.ASCII.GetString 
      let timestamp = DateTime.ParseExact(text,"yyyyMMddHHmmss",Thread.CurrentThread.CurrentCulture) 

      let size = BitConverter.ToUInt32(message.Data.[23..26] |> Array.rev,0) 
      let overwrite = 
       match message.Data.[27] with 
       | 0x00uy -> false 
       | _ -> true 

      //Create a FirmwareVersion instance 
      let firmware = new FirmwareVersion(); 
      firmware.Name <- name 
      firmware.Timestamp <- timestamp 
      firmware.Size <- size 

      let retVal = controller.StartLoadingFirmwareVersion(firmware,overwrite) 

      if retVal <> 0x00us then 
       this.nakResponse(message, retVal) //The controller denied the request 
      else 
       this.ackResponse(message); 

    //LoadFirmwareVersionBlock (ACK/NAK) 
    member private this.LoadFirmwareVersionBlock (message : Message) = 
     if message.Data.[0] <> 0x02uy then 
      this.nakResponse(message, VPL_INVALID) 
     else 
      //Analyze the data 
      let lastBlock = 
       match message.Data.[1] with 
       | 0x00uy -> false 
       | _true -> true 

      let blockNumber = BitConverter.ToUInt16(message.Data.[2..3] |> Array.rev,0)    
      let blockSize = BitConverter.ToUInt16(message.Data.[4..5] |> Array.rev,0) 
      let blockData = message.Data.[6..6 + (int)blockSize - 1] |> Array.rev 

      let retVal = controller.LoadFirmwareVersionBlock(lastBlock, blockNumber, blockSize, blockData) 

      if retVal <> 0x00us then 
       this.nakResponse(message, retVal) 
      else 
       this.ackResponse(message) 


    (* Helper methods *) 
    //We need to convert the DateTime instance to a byte[] understood by the device "yyyymmddhhmmss" 
    member private this.getTimeStampBytes (date : DateTime) = 
     let stringNumberToByte s = Byte.Parse(s.ToString()) //Casting to (byte) would give different results 

     let yearString = date.Year.ToString("0000") 
     let monthString = date.Month.ToString("00") 
     let dayString = date.Day.ToString("00") 
     let hourString = date.Hour.ToString("00") 
     let minuteString = date.Minute.ToString("00") 
     let secondsString = date.Second.ToString("00") 

     let y1 = stringNumberToByte yearString.[0] 
     let y2 = stringNumberToByte yearString.[1] 
     let y3 = stringNumberToByte yearString.[2] 
     let y4 = stringNumberToByte yearString.[3] 
     let m1 = stringNumberToByte monthString.[0] 
     let m2 = stringNumberToByte monthString.[1] 
     let d1 = stringNumberToByte dayString.[0] 
     let d2 = stringNumberToByte dayString.[1] 
     let h1 = stringNumberToByte hourString.[0] 
     let h2 = stringNumberToByte hourString.[1] 
     let min1 = stringNumberToByte minuteString.[0] 
     let min2 = stringNumberToByte minuteString.[1] 
     let s1 = stringNumberToByte secondsString.[0] 
     let s2 = stringNumberToByte secondsString.[1] 

     [| y1 ; y2 ; y3 ; y4 ; m1 ; m2 ; d1 ; d2 ; h1 ; h2 ; min1 ; min2 ; s1; s2 |] 

    //Sets the high bit of a byte to 1 
    member private this.setHigh (b : byte) : byte = 
     let array = new BitArray([| b |]) 
     array.[7] <- true 
     let mutable converted = [| 0 |] 
     array.CopyTo(converted, 0); 
     (byte)converted.[0] 

    //Build the header of a Message based on Intro + Data 
    member private this.buildHeader (intro : MessageIntro) (data : byte[]) = 
     let headerLength = 7; 
     let introLength = 7; 
     let length = (uint16)(headerLength + introLength + data.Length) 
     let crcData = ByteArrayExtender.Concat(intro.GetRawData(),data) 
     let crcValue = ByteArrayExtender.CalculateCRC16(crcData) 
     let lengthBytes = UShortExtender.ToIntelOrderedByteArray(length); 
     let crcValueBytes = UShortExtender.ToIntelOrderedByteArray(crcValue); 
     let headerChecksum = (byte)(SYNC_BYTE ^^^ lengthBytes.[0] ^^^ lengthBytes.[1] ^^^ crcValueBytes.[0] ^^^ crcValueBytes.[1]) 
     let header = new MessageHeader(); 
     header.Sync <- SYNC_BYTE 
     header.Length <- length 
     header.HeaderChecksum <- headerChecksum 
     header.DataChecksum <- crcValue 
     header 

    member private this.getStatusBytes = 
     let l = controller.Status 
     let status = (uint16)controller.Status 
     let statusBytes = BitConverter.GetBytes(status); 
     statusBytes |> Array.rev 

end 

(請注意,真正的源頭,這些類有不同的名稱,而不是「硬件」更具體)

我希望的建議,如何改善代碼,甚至不同的方式來處理這個問題。 例如,將使用動態語言 - 如IronPython使事情更容易, 我是在錯誤的方式去一起。你有什麼經驗,像這樣的問題, 你會改變什麼,避免,等等......

更新:

基於由Brian答案,我寫下了以下內容:

type DrCode9Item = {Name : string ; Timestamp : DateTime ; Size : uint32; Status : byte} 
type DrCode11Item = {Id : byte ; X : uint16 ; Y : uint16 ; SizeX : uint16 ; SizeY : uint16 
        Font : string ; Alignment : byte ; Scroll : byte ; Flash : byte} 
type DrCode12Item = {Id : byte ; X : uint16 ; Y : uint16 ; SizeX : uint16 ; SizeY : uint16} 
type DrCode14Item = {X : byte ; Y : byte} 

type DRType = 
| DrCode0 of byte 
| DrCode1 of byte * string * DateTime * uint32 * byte 
| DrCode2 of byte * byte * uint16 * uint16 * array<byte> 
| DrCode3 of byte * string 
| DrCode4 of byte * string * DateTime * byte * uint16 * array<byte> 
| DrCode5 of byte * uint16 
| DrCode6 of byte * DateTime 
| DrCode7 of byte * uint16 * uint16 
| DrCode8 of byte * uint16 * uint16 * uint16 * array<byte> 
| DrCode9 of byte * uint16 * array<DrCode9Item> 
| DrCode10 of byte * string * DateTime * uint32 * byte * array<byte> 
| DrCode11 of byte * array<DrCode11Item> 
| DrCode12 of byte * array<DrCode12Item> 
| DrCode13 of byte * uint16 * byte * uint16 * uint16 * string * byte * byte 
| DrCode14 of byte * array<DrCode14Item> 

我可以繼續做這一切的DR類型(相當多的), 但我仍然不明白這對我有什麼幫助。我已經在Wikibooks和F#的基礎上閱讀了關於它的 ,但是還沒有點擊我的頭像。

更新2

所以,我知道我能做到以下幾點:

let execute dr = 
    match dr with 
    | DrCode0(drCode) -> printfn "Do something" 
    | DrCode1(drCode, name, timestamp, size, options) -> printfn "Show the size %A" size 
    | _ ->() 
let date = DateTime.Now 

let x = DrCode1(1uy,"blabla", date, 100ul, 0uy) 

但是當消息進入的IMessageProcessor, 的的choise由右邊有什麼樣的信息呢是 然後調用正確的功能。以上只是 是額外的代碼,至少這是如何理解它, 所以我必須真的在這裏忽略了點...但我沒有看到它。

execute x 

回答

1

我認爲F#很自然地適合通過區分聯合表示這個域中的消息;我在想像

type Message = 
    | Message1 of string * DateTime * int * byte //name,timestamp,size,options 
    | Message2 of bool * short * short * byte[] //last,blocknum,blocksize,data 
    ... 

以及解析/解析來自/去往字節數組的消息的方法。正如你所說,這項工作很簡單,只是單調乏味。

我對消息的處理不太清楚,但總體上根據您的描述,聽起來像您有處理它。

我有點擔心你的'工具靈活性' - 你的約束是什麼? (例如.Net,必須由熟悉X,Y,Z技術的程序員來維護......)

+0

關於約束條件: 我們希望模擬器的核心是跨平臺的。 我們從不使用本機代碼,特別是不使用C代碼。 我已經看到C硬件開發人員編寫代碼的方式,這太可怕了!無處不在的縮略詞 第三方使用他們想要與模擬器通信的任何內容(通過tcp/ip或rs232)。 – TimothyP 2009-05-04 07:58:33

1

這是我的2美分(警告:我知道沒有F#):你有一個精細指定的輸入文件,即使具有完整的語法,也可以使用 。您想要將文件的內容映射到操作。因此,我建議你解析這個文件。 F#是一種功能語言,它可能適合稱爲Recursive Descent Parsing的解析技術。 The book "Expert F#"包含遞歸下降解析的討論。