我正在尝试从大部分自由格式的文本中解析一些信息。我尝试在 FParsec 中实现,但我之前没有使用过它,我不确定我是否做错了,或者即使它非常适合这个特定问题。
问题描述
我想从降价文档(“examplecode”和“requiredcode”标签)中解析出一组特定Liquid 标签的内容。降价将主要是自由格式的文本,偶尔会在 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" ]
.
我所追求的解析逻辑可以命令式地表达。遍历每一行,如果我们找到一个我们感兴趣的开始标签,取行直到我们匹配结束标签并将这些行添加到结果列表中,否则跳过行直到下一个开始标签。重复。
这可以通过循环或折叠或正则表达式来完成:
\{%\s*(examplecode|requiredcode).*\%}(.*?)\{%\s*end\1\s*%\}
我的 FParsec 尝试
我发现很难用 FParsec 来表达上面的逻辑。我想写类似的东西between s t (everythingUntil t)
,但我不知道如何在不everythingUntil
消耗结束令牌的情况下实现它,导致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 尝试的改进感兴趣。我很乐意根据需要将其拆分为其他问题。)