2014-10-07 58 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組合器以創建具有更多參數的組合器。)

前三行的解析器看起來並不完整。

+0

根據您的建議,我使用管道組合器編輯了更新代碼的問題;但是,我不滿足於去元組,然後到一個記錄。也許你有更直接的想法來創建記錄? – 2014-10-08 18:57:34

+0

使用「管道」組合器可以創建一個記錄,如在'pipe3 getVersion pipeOrigin getSessionName(fun vo sn - > {Version = v; Origin = o; SessionName = sn})''中,儘管在你的情況下,你仍然必須讓'getVersion'返回一個'uint16'和'pipeOrigin '所有者'。 – 2014-10-10 08:01:18

+0

嗯,是的,我已經使用您的建議更改更新了代碼,並獲得了所需的記錄。但是,我仍然想知道,如果可能具有自動屬性的對象比記錄更可取...尤其是,我繼續解析會話描述的其他行? – 2014-10-11 15:37:46