感谢您对此项目的my first post和my second post的回复。这个问题基本上与第一个问题相同,但我的代码根据收到的关于这两个问题的反馈进行了更新。如何递归调用我的解析器?
我挠挠脑袋,茫然地盯着代码。我不知道从哪里开始。那是我转向stackoverflow的时候。
我在代码注释中包含了我收到的编译时错误。一个绊脚石可能是我受歧视的联盟。我没有和被歧视的工会合作太多,所以我可能错误地使用了我的。
我正在使用的示例POST,我在前两个问题中包含的一些内容,包括一个边界,其中包含带有新边界的第二个帖子。第二个帖子包括由第二个边界分隔的几个附加部分。这几个附加部分中的每一个都是一个由标题和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 })
我尝试投入Reply ()
,但他们刚刚返回解析器,意味着上面的c
成为Parser<...>
而不是Content
。这似乎是一个倒退,或者至少在错误的方向。不过,我承认我的无知,欢迎更正!
答案 0 :(得分:0)
我可以帮助解决其中一个错误。
F#通常从左到右绑定参数,因此您需要在对pContent
的递归调用或管道向后运算符<|
周围使用括号来表明您要评估递归调用和绑定返回值。
还值得注意的是<|
与您的$
运营商相同。
Content.Post
不是 Post对象的构造函数。您需要一个函数来接受帖子列表并返回帖子。 (List module的某些内容是否符合您的要求?)
答案 1 :(得分:0)
我的第一个回答是完全错误的,但我以为我会把它留下来。
Post
和Content
类型定义为:
type Content =
| Content of string
| Post of Post list
and Post =
{ headers : Header list
; content : Content }
Post
是一个记录,Content
是一个被歧视的联盟。
F#将Discriminated Unions的案例视为与类型不同的命名空间。因此Content
与Content.Content
不同,Post
与Content.Post
不同。因为它们不同,所以具有相同的标识符令人困惑。
pContent
应该返回什么?如果它应该返回被歧视的联盟Content
,则需要在Post
案例中包装您在第一种情况下返回的Content.Post
记录,即
$ fun h c -> Post [ { headers=h
; content=Content $ unlines c } ]
(F#能够推断出&#39; Post&#39;引用Content.Post
案例,而不是此处的Post
记录类型。)