F #, FParsec и рекурсивный вызов потокового парсера
Я разрабатываю многочастный парсер MIME, используя F # и FParsec. Я разрабатываю итеративно, и поэтому это крайне неопределяемый, хрупкий код - он решает только мою первую непосредственную проблему. Красный, Зеленый, Рефакторинг.
Я должен анализировать поток, а не строку, которая действительно бросает меня за цикл. Учитывая это ограничение, насколько я понимаю, мне нужно рекурсивно вызывать синтаксический анализатор. Как это сделать, мне не по карману, по крайней мере, так, как я проделал это до сих пор.
namespace MultipartMIMEParser
open FParsec
open System.IO
type private Post = { contentType : string
; boundary : string
; subtype : string
; content : string }
type MParser (s:Stream) =
let ($) f x = f x
let ascii = System.Text.Encoding.ASCII
let str cs = System.String.Concat (cs:char list)
let q = "\""
let qP = pstring q
let pSemicolon = pstring ";"
let manyNoDoubleQuote = many $ noneOf q
let enquoted = between qP qP manyNoDoubleQuote |>> str
let skip = skipStringCI
let pContentType = skip "content-type: "
>>. manyTill anyChar (attempt $ preturn () .>> pSemicolon)
|>> str
let pBoundary = skip " boundary=" >>. enquoted
let pSubtype = opt $ pSemicolon >>. skip " type=" >>. enquoted
let pContent = many anyChar |>> str // TODO: The content parser needs to recurse on the stream.
let pStream = pipe4 pContentType pBoundary pSubtype pContent
$ fun c b t s -> { contentType=c; boundary=b; subtype=t; content=s }
let result s = match runParserOnStream pStream () "" s ascii with
| Success (r,_,_) -> r
| Failure (e,_,_) -> failwith (sprintf "%A" e)
let r = result s
member p.ContentType = r.contentType
member p.Boundary = r.boundary
member p.ContentSubtype = r.subtype
member p.Content = r.content
Первая строка примера POST выглядит следующим образом:
content-type: Multipart/related; boundary="RN-Http-Body-Boundary"; type="multipart/related"
Он занимает одну строку в файле. Другие части в содержании включаютcontent-type
значения, которые охватывают несколько строк, поэтому я знаю, что мне придется уточнить свои парсеры, если я хочу их использовать повторно.
Как-то я должен позвонитьpContent
с (строка?) результатамиpBoundary
так что я могу разделить остальную часть потока на соответствующие границы, а затем каким-то образом вернуть несколько частей для содержимого сообщения, каждая из которых будет отдельной публикацией, с заголовками и содержимым (которое, очевидно, должно быть чем-то другим чем строка). Моя голова кружится. Этот код уже кажется слишком сложным, чтобы разобрать одну строку.
Большое спасибо за понимание и мудрость!