2014-10-07 5 views
0

После завершения учебника 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, но все примеры проанализируют на дискриминационные союзы. Тип записи - лучший выбор для сопоставления результатов; или, есть ли лучший способ?

ответ

2

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

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

+0

По рекомендации, я отредактировал вопрос с обновленным кодом, используя комбинаторы труб; но, я еще не доволен переходом к кортежам, а затем к записи. Может быть, у вас было что-то более прямое в виду для создания записи? –

+0

Используя комбинатор 'pipe', вы можете создать запись, например, например. в 'pipe3 getVersion pipeOrigin getSessionName (fun vo sn -> {Version = v; Origin = o; SessionName = sn})', хотя в вашем случае вам все равно придется возвращать 'getVersion'' uint16' и 'pipeOrigin '' Владелец'. –

+0

А, да, я обновил код с вашими рекомендуемыми изменениями и дал желаемую запись. Мне все же интересно, хотя, возможно, объект с автоматическими свойствами был бы предпочтительнее записи ... esp., Поскольку я продолжаю разбирать дополнительные строки описания сеанса? –