Как использовать FParsec для анализа в записи или объекте?

После завершения учебника по FParsec я решил попробовать написать синтаксический анализатор для SDP (протокол описания сеанса RFC 4366) - хотя бы первые 3 строки. SDP указан в ABNF (RFC 4234)); поэтому я пытаюсь работать с этим.

В примечании в конце раздела 5.1 Руководства пользователя указано, как "вы начинаете с простых синтаксических анализаторов для конечных узлов вашей грамматики, а затем пошагово продвигаетесь вверх, пока в конечном итоге не получите анализатор для полной грамматики". это направление и подсказка из ответа Стефана на использование труб, вот что я имею сейчас:

open FParsec
open System.Net

// handy for debugging, supposedly, but I couldn't get it to work
let breakParse (p: Parser<_,_>) stream =
  p stream

// Input
let session = "v=0
o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5
s=SDP Seminar
"

type Sdp = 
  { Version : System.UInt16;
    Origin : Owner;
    SessionName : string }

and Owner = 
  { Username : string
    SessionId : string
    SessionVersion : string
    NetType : NetworkType
    AddrType : AddressType
    Address : UnicastAddress }

and NetworkType = 
  | Undefined
  | Internet

and AddressType = 
  | Undefined
  | IPv4
  | IPv6

and UnicastAddress =
  | IPaddress of System.Net.IPAddress
  | FQDomainName of string
  | ExternalAddress of string

let sep : Parser<unit, unit> = skipChar '='
let getValue typeChar = skipChar typeChar .>> sep
let many1Digit : Parser<string, unit> = many1Satisfy isDigit
let many1Hex : Parser<string, unit> = many1Satisfy isHex

let nonWhitespace : Parser<string, unit> = many1Satisfy (isNoneOf @" \n\r\t") 

//proto-version = %x76 "=" 1*DIGIT CRLF
let getVersion = getValue 'v' >>. many1Digit .>> spaces |>> System.Convert.ToUInt16

//origin-field = %x6f "=" username SP sess-id SP sess-version SP
//               nettype SP addrtype SP unicast-address CRLF
// username cannot contain whitespace; i.e., only visible chars
let getUsername : Parser<string, unit> = getValue 'o' >>. nonWhitespace .>> spaces

//sess-id = 1*DIGIT
let getSessionId = many1Digit .>> spaces

//sess-version = 1*DIGIT
let getSessionVersion = many1Digit .>> spaces

let getNetType : Parser<NetworkType, unit> = 
  pstring "IN" |>> (function 
  | "IN" -> NetworkType.Internet
  | _ -> NetworkType.Undefined)
  .>> spaces

let getAddrType : Parser<AddressType, unit> = 
  anyString 3 |>> (function 
  | "IP4" -> AddressType.IPv4
  | "IP6" -> AddressType.IPv6
  | _ -> AddressType.Undefined)
  .>> spaces

let getAddress : Parser<UnicastAddress, unit> = 
    (restOfLine true) |>> (fun a -> IPAddress.Parse a |> IPaddress )

let getUserSession = pipe3 getUsername getSessionId getSessionVersion (fun u i v -> (u, i, v))
let pipeOrigin = pipe4 getUserSession getNetType getAddrType getAddress 
              (fun us n t a -> 
                let u, i, v = us
                {Username=u; SessionId=i; SessionVersion=v; NetType=n; 
                  AddrType=t; Address=a})

//session-name-field =  %x73 "=" text CRLF
let getSessionName = getValue 's' >>. restOfLine true

let threelines = pipe3 getVersion pipeOrigin getSessionName 
              (fun v o sn -> {Version=v; Origin=o; SessionName=sn})

let sessionDesc = run threelines session

И это работает (за исключением того, что getAddress еще не обрабатывает FQDN или внешние адреса), с таким результатом:

val sessionDesc : ParserResult<Sdp,unit> =
  Success: {Version = 0us;
 Origin = {Username = "jdoe";
           SessionId = "2890844526";
           SessionVersion = "2890842807";
           NetType = Internet;
           AddrType = IPv4;
           Address = IPaddress 10.47.16.5;};
 SessionName = "SDP Seminar";}

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

Я прочитал в руководстве пользователя до раздела 5.4, но все примеры разбираются на дискриминационные союзы. Является ли тип записи лучшим выбором для сопоставления результатов; или есть лучший способ?

1 ответ

Вы могли бы использовать pipe функции для последовательного применения парсеров для строк, а затем для создания записи. (Если вам нужно более 5 временных, вы можете легко объединить несколько pipex комбинаторы для создания pipe комбинаторы с большим количеством аргументов.)

Ваши парсеры для первых трех строк пока не завершены.

Другие вопросы по тегам