2009-05-04 2 views
1

Это довольно длинный вопрос, поэтому, пожалуйста, со мной.Как бы вы решили эту проблему синтаксического анализа данных?

Мы реализуем эмулятор для аппаратного обеспечения, которое одновременно разрабатывается . Идея состоит в том, чтобы предоставить сторонним разработчикам программное решение для тестирования своего клиентского программного обеспечения и предоставить разработчикам разработчику контрольный пункт для реализации их прошивки.

Люди, которые написали протокол для аппаратного обеспечения, использовали пользовательскую версию SUN XDR, выполненную по заказу , называемую INCA_XDR. Это инструмент для сериализации и де-сериализовать сообщения. Он написан на C, и мы хотим избежать нативного кода , поэтому мы вручную обрабатываем данные протокола.

Протокол по своей природе довольно сложный и пакеты данных могут иметь много различных структур, но оно всегда имеет ту же глобальную структуру:

[ГОЛОВКА] [ИНТРО] [ДАННЫЕ] [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 об ошибках, некоторые дополнительные свойства, которые были добавлены к классу сообщений:

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

Они не связаны с протоколом и существуют только для IMessageProcessor

IMessageProcessor является где ведется настоящая работа. Из-за всех групп и типов сообщений, которые я решил использовать, используйте F # для реализации интерфейса IMessageProcessor, поскольку соответствие шаблону показалось хорошим способом избежать множества вложенных операторов if/else и caste. (у меня нет опыта работы с F # или другими, чем LINQ даже функциональными языками и SQL)

IMessageProcessor анализирует данные и решает, какие методы он должен вызвать на IHardwareController. Может показаться излишним иметь IHardwareController, , но мы хотим иметь возможность поменять его с другой реализацией и не будем вынуждены использовать F #. Текущая реализация - это окна WPF, , но это может быть окно Cocoa # или просто консоль, например.

IHardwareController также отвечает за управление состоянием, потому что разработчики должны иметь возможность манипулировать аппаратными параметрами и ошибками через пользовательский интерфейс.

Итак, как только IMessageProcessor вызвал правильные методы на IHardwareController, , он должен сгенерировать ответ MEssage. Опять же ... данные в этих сообщениях ответа могут иметь много разных структур.

В конце концов, 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 

(Пожалуйста, обратите внимание, что в реальном источнике, классы имеют разные имена, более конкретные, чем «Hardware»)

Я надеюсь на предложения, способы улучшения кода или даже различные способы решения проблемы. Например, использование динамического языка, такого как IronPython, упростит, Я собираюсь не так. Каков ваш опыт в таких проблемах, , что бы вы изменили, избегали и т. Д ....

Update:

На основании ответа Брайан, я записал следующее:

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 и в Foundations of F #, но что-то еще не щелкает в моей голове.

Update 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, ВЫБИРАЕМ сделан прямо там, какие сообщения он это и затем вызывается соответствующая функция. Выше было бы только быть дополнительным кодом, по крайней мере, так оно и есть, , поэтому я действительно должен упускать этот пункт здесь ... но я этого не вижу.

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

Вы сказали: «Я решил использовать F # для реализации интерфейса IMessageProcessor, потому что сопоставление образцов казалось хорошим способом избежать множества вложенных if/else и кастовые заявления ", и я согласен, но в частности, создание Message должно быть DU является ключевым здесь. Когда Message является DU, сопоставление образцов по сообщениям становится простым, и я думаю, что это самая большая победа. (Как еще вы представляете «Сообщение»?) – Brian

1

Вот мои 2 цента (предостережение: я не знаю F #): у вас есть точно заданный входной файл, даже с полной грамматикой. Вы хотите сопоставить содержимое файла с действиями. Поэтому я предлагаю вам проанализировать файл. F # является функциональным языком, он может соответствовать методу синтаксического анализа, который называется Recursive Descent Parsing. The book "Expert F#" содержит обсуждение рекурсивного синтаксического анализа.

Смежные вопросы