fparsec - 限制应用解析器的字符数

fparsec - limit number of characters that a parser is applied to

我遇到一个问题,在流的解析过程中,我指出需要通过多次(按顺序)应用特定解析器来解析接下来的 N 个字符。

(剥离玩具)示例:

17<tag><anothertag><a42...
  ^
  |- I'm here

假设 17 表示接下来的 N=17 个字符组成标签,所以我需要重复应用我的“tagParser”但在 17 个字符后停止并且不消耗其余部分,即使它看起来像标签,因为具有不同的含义,将被另一个解析器解析。

我不能使用 manymany1 因为那会吃掉超过 N 个字符的流。 我也不能用parray,因为我不知道N个字符内有多少个解析器成功的应用

我正在研究 manyMinMaxSatisfy 但无法弄清楚在这种情况下如何使用它。

有没有办法切割流的 N 个字符并将它们提供给某些解析器?或者有没有办法调用许多应用程序但最多 N 个字符?

谢谢。

您可以使用 getPosition 来确保您没有超过指定的字符数。我把它放在一起(使用 F# 6),它似乎可以工作,尽管 simpler/faster 解决方案可能是可能的:

let manyLimit nChars p =
    parse {
        let! startPos = getPosition

        let rec loop values =
            parse {
                let! curPos = getPosition
                let nRemain = (startPos.Index + nChars) - curPos.Index
                if nRemain = 0 then
                    return values
                elif nRemain > 0 then
                    let! value = p
                    return! loop (value :: values)
                else
                    return! fail $"limit exceeded by {-nRemain} chars"
            }

        let! values = loop []
        return values |> List.rev
    }

测试代码:

let ptag =
    between
        (skipChar '<')
        (skipChar '>')
        (manySatisfy (fun c -> c <> '>'))
    
let parser =
    parse {
        let! nChars = pint64
        let! tags = manyLimit nChars ptag
        let! rest = restOfLine true
        return tags, rest
    }

run parser "17<tag><anothertag><a42..."
    |> printfn "%A"

输出为:

Success: (["tag"; "anothertag"], "<a42...")

相当低级的解析器,对原始 Reply 对象进行操作。它读取字符数,创建子字符串以提供给标签解析器并消耗剩余部分。应该有更简单的方法,但我对 FParsec

没有太多经验
open FParsec

type Tag = Tag of string

let pTag = // parses tag string and constructs 'Tag' object
    skipChar '<' >>. many1Satisfy isLetter .>> skipChar '>' 
    |>> Tag

let pCountPrefixedTags stream =
    let count = pint32 stream // read chars count
    if count.Status = Ok then
        let count = count.Result
        // take exactly 'count' chars
        let tags = manyMinMaxSatisfy count count (fun _ -> true) stream
        if tags.Status = Ok then
            // parse substring with tags
            let res = run (many1 pTag) tags.Result
            match res with
            | Success (res, _, _) -> Reply(res)
            | Failure (_, error, _) -> Reply(ReplyStatus.Error, error.Messages)
        else
            Reply(tags.Status, tags.Error)
    else
        Reply(count.Status, count.Error)

let consumeStream =
    many1Satisfy (fun _ -> true)

run (pCountPrefixedTags .>>. consumeStream) "17<tag><anothertag><notTag..."
|> printfn "%A" // Success: ([Tag "tag"; Tag "anothertag"], "<notTag...")

您也可以在不进入流级别的情况下执行此操作。

open FParsec

let ptag =
    between
        (skipChar '<')
        (skipChar '>')
        (manySatisfy (fun c -> c <> '>'))

let tagsFromChars (l: char[]) =
    let s = new System.String(l)
    match run (many ptag) s with
    | Success(result, _, _) -> result
    | Failure(errorMsg, _, _) -> []

let parser =
    parse {
        let! nChars = pint32
        let! tags = parray nChars anyChar |>> tagsFromChars
        let! rest = restOfLine true
        return tags, rest
    }

run parser "17<tag><anothertag><a42..."
    |> printfn "%A"