2014-10-07 3 views
0

FParsec 튜토리얼을 마친 후 SDP (세션 설명 프로토콜 RFC 4366)에 대한 파서를 작성하기로 결정했습니다. 적어도 처음 세 줄. SDP는 ABNF (RFC 4234)에 명시되어 있습니다. 그래서, 나는 그걸로 노력하고 있어요.FParsec을 사용하여 레코드 또는 개체를 구문 분석하는 방법?

사용자 가이드 섹션 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 또는 외부 주소를 처리하지 않습니다 제외)는이 결과, 작동 : 문법 "그 방향과 파이프를 사용하는 스테판의 대답에서 tipss으로, 여기에 내가 지금 무슨이다 :

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 개 이상인 경우 더 많은 인수를 가진 pipe 연결자를 만들기 위해 여러 pipex 결합자를 쉽게 결합 할 수 있습니다.)

처음 세 줄에 대한 파서가 아직 완성되지 않은 것 같습니다.

+0

귀하의 추천에 따라 파이프 연결자를 사용하여 업데이트 된 코드로 질문을 편집했습니다. 그러나 나는 아직 튜플에 가면서 만족스럽지 못하다. 기록을 세우는 데 더 직접적인 것을 염두에 두셨 을까요? –

+0

'파이프'조합을 사용하면 예를 들어 다음과 같은 레코드를 생성 할 수 있습니다. 귀하의 경우에는'getVersion'을'uint16'과'pipeOrigin'을 리턴해야합니다.'getVersion'은'getVersion'을 리턴해야합니다. ''소유자 '. –

+0

아, 예, 권장 변경 내용으로 코드를 업데이트했으며 원하는 레코드를 생성합니다. 그래도 자동 속성을 가진 객체가 레코드보다 좋을지 궁금합니다 ... 특히 세션 설명의 추가 행을 구문 분석 할 때? –