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

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

提问人:Friedrich Gretz 提问时间:1/4/2022 更新时间:1/26/2022 访问量:116

问:

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

(剥离玩具)例:

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

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

我不能使用 or 因为这会吃掉超出这 N 个字符的流。 我也不能使用,因为我不知道该解析器在 N 个字符中有多少个成功的应用程序。manymany1parray

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

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

谢谢。

f# fparsec

评论


答:

3赞 Brian Berns 1/4/2022 #1

您可以使用它来确保不会超过指定的字符数。我把它放在一起(使用 F# 6),它似乎有效,尽管可能有更简单/更快的解决方案:getPosition

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...")

评论

0赞 Friedrich Gretz 1/4/2022
我想知道我是否可以使用 getPosition 并检查我已经走了多远,但我未能构建循环。此外,我不知道这里派上用场的一元语法。多谢!
0赞 JL0PD 1/4/2022
@FriedrichGretz,不建议使用一元语法: quanttec.com/fparsec/users-guide/where-is-the-monad.html
0赞 Brian Berns 1/4/2022
小心 - 仅“当性能有问题时”才不建议这样做。大多数时候,它很棒,而且我发现它更容易使用(而且在大多数情况下足够快)。
2赞 JL0PD 1/4/2022 #2

相当低级的解析器,对原始对象进行操作。它读取字符计数,创建子字符串以馈送到标记解析器并使用 rest。应该有一种更简单的方法,但我对 FParsec 没有太多经验Reply

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...")
1赞 Founder Fang 1/26/2022 #3

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

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"