提问人:Friedrich Gretz 提问时间:1/4/2022 更新时间:1/26/2022 访问量:116
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 个字符后停止,即使它看起来像一个标签,也不会消耗其余的,因为它有不同的含义,并且将被另一个解析器解析。
我不能使用 or 因为这会吃掉超出这 N 个字符的流。
我也不能使用,因为我不知道该解析器在 N 个字符中有多少个成功的应用程序。many
many1
parray
我正在研究,但无法弄清楚在这种情况下如何使用它。manyMinMaxSatisfy
有没有办法剪切流的 N 个字符并将它们提供给某个解析器?或者有没有办法调用许多应用程序,但最多 N 个字符?
谢谢。
答:
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"
评论