После завершения учебника FParsec я решил попробовать написать парсер для SDP (протокол описания сеанса RFC 4366) - по крайней мере, первые 3 строки. SDP указан в ABNF (RFC 4234)); поэтому, я пытаюсь работать от этого.Как использовать FParsec для анализа в записи или объекте?
Примечание в конце руководства пользователя В разделе 5.1 указано, что «вы начинаете с простых парсеров для листовых узлов вашей грамматики, а затем шаг за шагом прокладываете свой путь, пока не получите в конечном итоге парсер для полного . грамматика»с этим направлением и TIPSS от ответа Стефана использовать трубы, вот что у меня сейчас:
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, но все примеры проанализируют на дискриминационные союзы. Тип записи - лучший выбор для сопоставления результатов; или, есть ли лучший способ?
По рекомендации, я отредактировал вопрос с обновленным кодом, используя комбинаторы труб; но, я еще не доволен переходом к кортежам, а затем к записи. Может быть, у вас было что-то более прямое в виду для создания записи? –
Используя комбинатор 'pipe', вы можете создать запись, например, например. в 'pipe3 getVersion pipeOrigin getSessionName (fun vo sn -> {Version = v; Origin = o; SessionName = sn})', хотя в вашем случае вам все равно придется возвращать 'getVersion'' uint16' и 'pipeOrigin '' Владелец'. –
А, да, я обновил код с вашими рекомендуемыми изменениями и дал желаемую запись. Мне все же интересно, хотя, возможно, объект с автоматическими свойствами был бы предпочтительнее записи ... esp., Поскольку я продолжаю разбирать дополнительные строки описания сеанса? –