gpt4 book ai didi

f# - 使用 FParsec 从大部分自由格式的文本中挑选 block

转载 作者:行者123 更新时间:2023-12-04 19:57:12 27 4
gpt4 key购买 nike

我试图从大部分自由格式的文本中解析一些信息。我尝试在 FParsec 中实现,但我之前没有使用过它,我不确定我是否做错了,或者即使它非常适合这个特定问题。

问题描述

我想解析出一组特定 Liquid tags 的内容来自 Markdown 文档(“examplecode”和“requiredcode”标签)。 Markdown 将主要是自由格式的文本,在 Liquid 标签中偶尔会有块,例如:

Some free form text.
Possibly lots of lines. Maybe `code` stuff.

{% examplecode opt-lang-tag %}
ABC
DEF
{% endexamplecode %}

More text. Possibly multilines.

{% othertag %}
can ignore this tag
{% endothertag %}

{% requiredcode %}
GHI
{% endrequiredcode %}

在这种情况下,我需要解析出 [ "ABC\nDEF"; "GHI" ] .

我所追求的解析逻辑可以命令式地表达。循环遍历每一行,如果我们找到我们感兴趣的开始标记,则取行直到我们匹配结束标记并将这些行添加到结果列表中,否则跳过行直到下一个开始标记。重复。

这可以通过循环或折叠来完成,或者 with a regular expression :

\{%\s*(examplecode|requiredcode).*\%}(.*?)\{%\s*end\1\s*%\}

我的 FParsec 尝试

我发现很难在 FParsec 中表达上面的逻辑。我想写一些类似 between s t (everythingUntil t) 的东西,但我不知道如何在没有 everythingUntil 的情况下实现它消耗结束 token ,导致 between失败。

我最终得到了以下内容,它不处理 "{%" 的嵌套事件,但似乎通过了我关心的主要测试用例:
let trimStr (s : string) = s.Trim()
let betweenStr s t = between (pstring s) (pstring t)
let allTill s = charsTillString s false maxInt
let skipAllTill s = skipCharsTillString s false maxInt
let word : Parser<string, unit> = many1Satisfy (not << Char.IsWhiteSpace)

type LiquidTag = private LiquidTag of name : string * contents : string
let makeTag n c = LiquidTag (n, trimStr c)

let liquidTag =
let pStartTag = betweenStr "{%" "%}" (spaces >>. word .>> spaces .>> skipAllTill "%}")
let pEndTag tagName = betweenStr "{%" "%}" (spaces >>. pstring ("end" + tagName) .>> spaces)
let tagContents = allTill "{%"
pStartTag >>= fun name ->
tagContents
.>> pEndTag name
|>> makeTag name

let tags = many (skipAllTill "{%" >>. liquidTag)

然后我可以过滤标签以仅包含我感兴趣的标签。

这比基本实现(如正则表达式)所做的要多得多,例如描述性错误报告和更严格的输入格式验证(这有好有坏)。

更严格格式的后果之一是在嵌套 "{%" 上解析失败。标签内的子串。我不确定如何调整它来处理这种情况(应该给 [ "ABC {% DEF " ] ):
{% examplecode %}
ABC {% DEF
{% endexamplecode %}



有没有办法更接近地表达 FParsec 中“问题描述”部分中描述的逻辑,或者输入的自由格式性质是否使 FParsec 比更基本的循环或正则表达式更不适合这个?

(我也对在标签中允许嵌套 "{%" 字符串以及对我的 FParsec 尝试的改进感兴趣。我很高兴根据需要将其拆分为其他问题。)

最佳答案

我只是使用 start >>. everythingUntil end而不是 between start end body .

下面的实现比较接近regex中的逻辑:

let maxInt = System.Int32.MaxValue    
type LiquidTag = LiquidTag of string * string

let skipTillString str = skipCharsTillString str true maxInt

let skipTillStringOrEof str : Parser<unit, _> =
fun stream ->
let mutable found = false
stream.SkipCharsOrNewlinesUntilString(str, maxInt, &found) |> ignore
Reply(())

let openingBrace = skipString "{%" >>. spaces

let tagName name =
skipString name
>>? nextCharSatisfies (fun c -> c = '%' || System.Char.IsWhiteSpace(c))

let endTag name =
openingBrace >>? (tagName ("end" + name) >>. (spaces >>. skipString "%}"))

let tagPair_afterOpeningBrace name =
tagName name >>. skipTillString "%}"
>>. (manyCharsTill anyChar (endTag name)
|>> fun str -> LiquidTag(name, str))

let skipToOpeningBraceOrEof = skipTillStringOrEof "{%"

let tagPairs =
skipToOpeningBraceOrEof
>>. many (openingBrace
>>. opt ( tagPair_afterOpeningBrace "examplecode"
<|> tagPair_afterOpeningBrace "requiredcode")
.>> skipToOpeningBraceOrEof)
|>> List.choose id
.>> eof

一些注意事项:
  • 我只解析你感兴趣的两个 Liquid 语句。这个
    如果这些语句之一嵌套在
    你不感兴趣的陈述。它还有一个优点是没有
    解析器必须在解析器运行时构建。
  • 我正在使用 >>?组合器来控制何时准确回溯
    可能导致。
  • 这种实现的性能不会很好,但是如果需要,可以通过多种方式对其进行优化。最慢的组件可能是 manyCharsTill anyChar (endTag name)解析器,可以很容易地用自定义原语替换。 many ... |> List.choose idtagPairs也可以轻松替换为更高效的自定义组合器。
  • 关于f# - 使用 FParsec 从大部分自由格式的文本中挑选 block ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21921877/

    27 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com