2009-05-04 5 views
1

다소 오랜 질문이므로 제발 나와 함께하십시오.이 데이터 구문 분석 문제를 어떻게 푸시겠습니까?

이 동시에 개발되는 하드웨어 에뮬레이터를 구현하고 있습니다. 아이디어는 자신의 클라이언트 소프트웨어를 테스트하고 하드웨어 개발자에게 펌웨어를 구현하기위한 참조 포인트를 제공하는 소프트웨어 솔루션을 제 3 자에게 제공하는 것입니다.

하드웨어 용 프로토콜을 작성한 사람은 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에 응답 메시지 또는 기타 메시지의 의미가 이 아니므로 원시 데이터가 유효하지 않다고 판단되는 경우에도 원시 데이터의 유효성 검사 만 수행됩니다.

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

그들은 프로토콜 관련 만 IMessageProcessor 인 IMessageProcessor

존재하지 않습니다 :

오류에 대한 IMessageProcessor 통보하려면 몇 가지 추가 속성은 메시지 클래스에 추가되었습니다 실제 작업이 완료된 곳입니다. 모든 다른 메시지 그룹 및 유형의 나는 패턴 일치하기 때문에 IMessageProcessor 인터페이스를 구현 사용 F 번호로 결정 때문에 경우/다른 사람과 계급 문 중첩을 많이 방지 할 수있는 좋은 방법처럼 보였다. 는

IMessageProcessor 데이터를 분석하고 그것이 IHardwareController에 를 호출해야하는지 방법 결정 (I는 F # 또는 LINQ 이외도 함수형 언어와 SQL에 대한 사전 경험이 없음). IHardwareController, 을 가지고 있어도 과장된 것처럼 보일 수도 있지만, 다른 구현물 인 으로 교체 할 수 있기를 원하며 F #을 사용하지 않아도됩니다. 현재 구현은 WPF 윈도우 인 이지만 Cocoa # 창 또는 단순히 예를 들어 콘솔 일 수 있습니다.

개발자가 사용자 인터페이스를 통해 하드웨어 매개 변수와 오류를 조작 할 수 있어야하므로 IHardwareController도 상태 관리를 담당합니다.

그래서 IMessageProcessor가 IHardwareController에서 올바른 메서드를 호출하면 MEssage 응답을 생성해야합니다. 다시 ...이 응답 메시지의 데이터 은 여러 가지 구조를 가질 수 있습니다.

결국 IDataFactory는 메시지를 원시 프로토콜 데이터 으로 변환하여 통신을 담당하는 클래스로 보낼 준비가 된 것입니다.

이이 코드를 작성하는 방법에 대한 "하드"아무것도 없다 (데이터의 추가 캡슐 예를 들어해야 할 수도 있습니다)하지만, 모든 다른 명령과 데이터 구조는 많은 코드를 많이 필요로하고 몇 것들이 우리가 있습니다 재사용 할 수 있습니다. 이것은 내가 F 번호를 사용하는 것은 이번이 처음이다

(적어도 지금까지의 내가 지금 볼 수있는, 희망 누군가가 나에게 잘못을 증명할 수), 내가 가서 실제로 배우는 중이에요. 아래의 코드는 끝내야 만합니다. 아마 거대한 혼란처럼 보입니다. 그것은 오직 프로토콜 에있는 모든 메시지의 handfull을 구현하고 나는 그들 중 많은 것들을 많이 말할 수 있습니다. 그래서이 파일은 커질 것입니다!

아는 것이 중요합니다 : 바이트 순서가 와이어를 통해 반전 (역사적인 이유로)

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의 같은 동적 언어의 사용이 일을 더 쉽게 만들 것 , 내가 모두 함께 잘못된 방향으로 가고있다. 이 같은 문제에 대한 사용자의 경험은 무엇, 변경 될지, 등, 피 ....

업데이트 : 브라이언하여 답변에 따라

, 내가 적어 다음

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

+0

여기에 차별 노동 조합과 함께 할 것이 무엇인지 설명 하시겠습니까? 나는 그들을 정의하는 방법을 알고 있지만 그들이 나를 도울 방법을 모르겠다 ... – TimothyP

+1

당신은 말했다 : 패턴 매칭은 중첩 된 if/else를 피하기위한 좋은 방법처럼 보였기 때문에 "나는 IMessageProcessor 인터페이스를 구현하기 위해 F #을 사용하기로 결정했다. 그리고 계급 성명서 "와 나는 동의하지만 특히 메시지를 하위 사용자로 만드는 것이 핵심이다. 메시지가 하위 사용자 인 경우 메시지의 패턴 일치가 간단 해지며 이것이 가장 큰 승리라고 생각합니다. (당신은 어떻게 '메시지'를 표현하겠습니까?) – Brian

1

여기 내 2 센트입니다 (경고 : F #을 안다). 완전한 문법으로도 정확하게 입력 된 파일 . 파일의 내용을 작업에 매핑하려고합니다. 따라서 파일을 구문 분석하는 것이 좋습니다. F #이 기능적 언어이므로 Recursive Descent Parsing이라는 구문 분석 기술에 부합 할 수 있습니다. The book "Expert F#"에는 재귀 적 하향 파싱에 대한 설명이 포함되어 있습니다.