1

このプロジェクトの最初の投稿2 番目の投稿に返信していただきありがとうございます。この質問は基本的に最初の質問と同じですが、これら 2 つの質問に対して受け取ったフィードバックに従ってコードが更新されています。パーサーを再帰的に呼び出すにはどうすればよいですか?

私は頭をかきむしりながら、ぼんやりとコードを見つめています。ここからどこへ行けばいいのかわからない。そんなときは、stackoverflow に目を向けます。

受け取ったコンパイル時エラーをコード コメントに含めました。つまずきのブロックの 1 つは、私の差別された組合かもしれません。私は差別された組合とあまり仕事をしたことがないので、私の使い方が間違っているかもしれません。

私が取り組んでいる POST の例 (前の 2 つの質問にその一部を含めた) は、新しい境界を持つ 2 番目の投稿を含む 1 つの境界で構成されています。その 2 番目の投稿には、2 番目の境界で区切られたいくつかの追加パーツが含まれています。これらのいくつかの追加部分はそれぞれ、ヘッダーと XML で構成される新しい投稿です。

このプロジェクトでの私の目標は、C# ソリューションで使用するライブラリを構築することです。このライブラリは、ストリームを受け取り、ヘッダーとパーツに再帰的に解析された POST を返します。ここで F# を輝かせたい思っています。

namespace MultipartMIMEParser

open FParsec
open System.IO

type Header = { name  : string
              ; value : string
              ; addl  : (string * string) list option }

type Content = Content of string
             | Post of Post list
and Post = { headers : Header list
           ; content : Content }

type UserState = { Boundary : string }
  with static member Default = { Boundary="" }

module internal P =
  let ($) f x = f x
  let undefined = failwith "Undefined."
  let ascii = System.Text.Encoding.ASCII
  let str cs = System.String.Concat (cs:char list)

  let makeHeader ((n,v),nvps) = { name=n; value=v; addl=nvps}

  let runP p s = match runParserOnStream p UserState.Default "" s ascii with
                 | Success (r,_,_) -> r
                 | Failure (e,_,_) -> failwith (sprintf "%A" e)

  let blankField = parray 2 newline

  let delimited d e =
      let pEnd = preturn () .>> e
      let part = spaces
                 >>. (manyTill
                      $ noneOf d
                      $ (attempt (preturn () .>> pstring d)
                                  <|> pEnd)) |>> str
       in part .>>. part

  let delimited3 firstDelimiter secondDelimiter thirdDelimiter endMarker =
      delimited firstDelimiter endMarker
      .>>. opt (many (delimited secondDelimiter endMarker
                      >>. delimited thirdDelimiter endMarker))

  let isBoundary ((n:string),_) = n.ToLower() = "boundary"

  let pHeader =
      let includesBoundary (h:Header) = match h.addl with
                                        | Some xs -> xs |> List.exists isBoundary
                                        | None    -> false
      let setBoundary b = { Boundary=b }
       in delimited3 ":" ";" "=" blankField
          |>> makeHeader
          >>= fun header stream -> if includesBoundary header
                                   then
                                     stream.UserState <- setBoundary (header.addl.Value
                                                                      |> List.find isBoundary
                                                                      |> snd)
                                     Reply ()
                                   else Reply ()

  let pHeaders = manyTill pHeader $ attempt (preturn () .>> blankField)

  let rec pContent (stream:CharStream<UserState>) =
      match stream.UserState.Boundary with
      | "" -> // Content is text.
              let nl = System.Environment.NewLine
              let unlines (ss:string list) = System.String.Join (nl,ss)
              let line = restOfLine false
              let lines = manyTill line $ attempt (preturn () .>> blankField)
               in pipe2 pHeaders lines
                        $ fun h c -> { headers=h
                                     ; content=Content $ unlines c }
      | _  -> // Content contains boundaries.
              let b = "--" + stream.UserState.Boundary
              // VS complains about pContent in the following line: 
              // Type mismatch. Expecting a
              //    Parser<'a,UserState>
              // but given a
              //    CharStream<UserState> -> Parser<Post,UserState>
              // The type 'Reply<'a>' does not match the type 'Parser<Post,UserState>'
              let p = pipe2 pHeaders pContent $ fun h c -> { headers=h; content=c }
               in skipString b
                  >>. manyTill p (attempt (preturn () .>> blankField))
                  // VS complains about Content.Post in the following line: 
                  // Type mismatch. Expecting a
                  //     Post list -> Post
                  // but given a
                  //     Post list -> Content
                  // The type 'Post' does not match the type 'Content'
                  |>> Content.Post

  // VS complains about pContent in the following line: 
  // Type mismatch. Expecting a
  //    Parser<'a,UserState>    
  // but given a
  //    CharStream<UserState> -> Parser<Post,UserState>
  // The type 'Reply<'a>' does not match the type 'Parser<Post,UserState>'
  let pStream = runP (pipe2 pHeaders pContent $ fun h c -> { headers=h; content=c })


type MParser (s:Stream) =
  let r = P.pStream s

  let findHeader name =
    match r.headers |> List.tryFind (fun h -> h.name.ToLower() = name) with
    | Some h -> h.value
    | None   -> ""

  member p.Boundary =
    let header = r.headers
                 |> List.tryFind (fun h -> match h.addl with
                                           | Some xs -> xs |> List.exists P.isBoundary
                                           | None    -> false)
     in match header with
        | Some h -> h.addl.Value |> List.find P.isBoundary |> snd
        | None   -> ""
  member p.ContentID = findHeader "content-id"
  member p.ContentLocation = findHeader "content-location"
  member p.ContentSubtype = findHeader "type"
  member p.ContentTransferEncoding = findHeader "content-transfer-encoding"
  member p.ContentType = findHeader "content-type"
  member p.Content = r.content
  member p.Headers = r.headers
  member p.MessageID = findHeader "message-id"
  member p.MimeVersion = findHeader "mime-version"

編集

これまでに受け取ったフィードバック (ありがとう!) に応えて、次の調整を行い、注釈付きのエラーを受け取りました。

let rec pContent (stream:CharStream<UserState>) =
    match stream.UserState.Boundary with
    | "" -> // Content is text.
            let nl = System.Environment.NewLine
            let unlines (ss:string list) = System.String.Join (nl,ss)
            let line = restOfLine false
            let lines = manyTill line $ attempt (preturn () .>> blankField)
             in pipe2 pHeaders lines
                      $ fun h c -> { headers=h
                                   ; content=Content $ unlines c }
    | _  -> // Content contains boundaries.
            let b = "--" + stream.UserState.Boundary
            // The following complaint is about `pContent stream`:
            // This expression was expected to have type
            //     Reply<'a>    
            // but here has type
            //     Parser<Post,UserState>
            let p = pipe2 pHeaders (fun stream -> pContent stream) $ fun h c -> { headers=h; content=c }
             in skipString b
                >>. manyTill p (attempt (preturn () .>> blankField))
                // VS complains about the line above:
                // Type mismatch. Expecting a
                //     Parser<Post,UserState>    
                // but given a
                //     Parser<'a list,UserState>    
                // The type 'Post' does not match the type ''a list'

// See above complaint about `pContent stream`. Same complaint here.
let pStream = runP (pipe2 pHeaders (fun stream -> pContent stream) $ fun h c -> { headers=h; content=c })

s を投げてみましReply ()たが、パーサーが返されただけで、c上記は.Parser<...>ではなくa になりましたContent。それは一歩後退したか、少なくとも間違った方向に進んでいたようです. しかし、私は自分の無知を認め、訂正を歓迎します!

4

2 に答える 2

0

エラーの 1 つを解決することができます。

F# は通常、引数を左から右にバインドするため、再帰呼び出しを括弧で囲むpContentか、逆パイプ演算子を使用<|して、再帰呼び出しを評価し、戻り値をバインドすることを示す必要があります。

<|オペレーターと同じであることも注目に値します$

Content.PostPost オブジェクトのコンストラクタではありません。Post リストを受け取り、Post を返す関数が必要です。(リストモジュールの何かが必要なことをしますか?)

于 2014-11-13T05:22:02.020 に答える
0

私の最初の答えは完全に間違っていましたが、そのままにしておこうと思っていました。

タイプPostContentは次のように定義されます。

type Content =
    | Content of string
    | Post of Post list
and Post =
    { headers : Header list
    ; content : Content }

PostレコードでありContent、差別された組合です。

F# は、判別共用体のケースを型とは別の名前空間として扱います。SoContentは とは異なりContent.Content、 は とPostは異なりContent.Postます。それらは異なるため、同じ識別子を持つことは混乱を招きます。

何がpContent戻ってくるはずですか?Discriminated Union を返すことになっている場合は、最初のケースで返すレコードContentをラップする必要があります。PostContent.Post

$ fun h c -> Post [ { headers=h
                    ; content=Content $ unlines c } ]

(F# は、'Post' がレコード タイプでContent.Postはなくケースを参照していると推測できます。)Post

于 2014-11-14T01:25:45.413 に答える