【问题标题】:Recursive parsing grammar consumes input and fails to parse sequence递归解析语法消耗输入,无法解析序列
【发布时间】:2019-06-10 05:02:01
【问题描述】:

我正在尝试编写一个Augmented Backus-Naur form 解析器。但是,每当我尝试解析替代方案时,都会遇到堆栈溢出异常。以下是触发问题的示例:

#r @"..\packages\FParsec\lib\net40-client\FParsecCS.dll"
#r @"..\packages\FParsec\lib\net40-client\FParsec.dll"
open FParsec

type Parser<'t> = Parser<'t, unit>

type Element =
    | Alternates of Element list
    | ParsedString of string

let (pRuleElement, pRuleElementRef) : (Parser<Element> * Parser<Element> ref) = createParserForwardedToRef()

let pString =
    pchar '"' >>. manyCharsTill (noneOf ['"']) (pchar '"')
    |>> ParsedString

let pAlternates : Parser<_> =
    sepBy1 pRuleElement (many (pchar ' ') >>. (pchar '/') >>. many (pchar ' ') )
    |>> Alternates

do pRuleElementRef :=
    choice
        [
            pString
            pAlternates
        ]

"\"0\" / \"1\" / \"2\" / \"3\" / \"4\" / \"5\" / \"6\" / \"7\""
|> run (pRuleElement .>> (skipNewline <|> eof))

只需像这样重新排序 choice 即可轻松解决该问题:

do pRuleElementRef :=
    choice
        [
            pAlternates
            pString
        ]

但是,这会导致堆栈溢出,因为它会不断尝试解析新的替代序列而不消耗输入。此外,该方法会破坏 ABNF 优先级:

  1. 字符串,名称形成
  2. 评论
  3. 取值范围
  4. 重复
  5. 分组,可选
  6. 串联
  7. 另类

我的问题基本上可以归结为:如何结合对可以是元素序列或元素的单个实例的单个元素的解析?如果您需要任何说明/其他示例,请告诉我。

非常感谢您的帮助,谢谢!

编辑:

我可能应该提到还有各种其他类型的分组。一个序列组(element[s]) 和一个可选组[optional element[s]。其中element 可以是嵌套组/可选组/字符串/其他元素类型。下面是一个序列组解析的示例(为简单起见,不包括可选的组解析):

#r @"..\packages\FParsec\lib\net40-client\FParsecCS.dll"
#r @"..\packages\FParsec\lib\net40-client\FParsec.dll"
open FParsec

type Parser<'t> = Parser<'t, unit>

type Element =
    | Alternates of Element list
    | SequenceGroup of Element list
    | ParsedString of string

let (pRuleElement, pRuleElementRef) : (Parser<Element> * Parser<Element> ref) = createParserForwardedToRef()

let pString =
    pchar '"' >>. manyCharsTill (noneOf ['"']) (pchar '"')
    |>> ParsedString

let pAlternates : Parser<_> =
    pipe2
        (pRuleElement .>> (many (pchar ' ') >>. (pchar '/') >>. many (pchar ' ')))
        (sepBy1 pRuleElement (many (pchar ' ') >>. (pchar '/') >>. many (pchar ' ') ))
        (fun first rest -> first :: rest)
    |>> Alternates

let pSequenceGroup : Parser<_> =
    between (pchar '(') (pchar ')') (sepBy1 pRuleElement (pchar ' '))
    |>> SequenceGroup

do pRuleElementRef :=
    choice
        [
            pAlternates
            pSequenceGroup
            pString
        ]

"\"0\" / ((\"1\" \"2\") / \"2\") / \"3\" / (\"4\" / \"5\") / \"6\" / \"7\""
|> run (pRuleElement .>> (skipNewline <|> eof))

如果我首先尝试解析备用/序列组,它会以 stack overflow 异常终止,因为它会尝试重复解析备用。

【问题讨论】:

    标签: parsing recursion f# fparsec


    【解决方案1】:

    问题是,当您在输入上运行 pRuleElement 解析器时,它会正确解析一个字符串,留下一些未使用的输入,但随后会在 choice 之外失败,这会回溯。

    您可以在主输入上运行pAlternates 解析器,它确实有效:

    "\"0\" / \"1\" / \"2\" / \"3\" / \"4\" / \"5\" / \"6\" / \"7\""
    |> run (pAlternates .>> (skipNewline <|> eof))
    

    我怀疑您可能只是这样做 - pAlternates 解析器可以正常工作,即使只处理单个字符串 - 它只会返回包含单例列表的 Alternates

    【讨论】:

    • 您好托马斯,感谢您的回复!我已经相应地编辑了我的问题。
    • @ChrisAltig 我认为区分pStringpSequenceGroup 会很好,因为这两个字符的第一个字符不同——即,如果不是其中之一,解析器将失败。但是,pAlternates 仍然是需要在顶层运行的那个,以便它可以一个接一个地消耗 - 所以我认为你需要两个解析器 - 一个用于可以由第一个字符确定的任何内容,并且另一个用于通过alternates进行迭代。
    • 你读懂了我的心思。实际上,我刚刚完成了一个类似解决方案的编程,并且在您发表评论时正在准备我的答案。我还得出结论,排序不(不能?)匹配维基百科链接上列出的优先顺序。不过,我认为这无关紧要。
    【解决方案2】:

    看起来解决方案只是在解析备选方案时不尝试解析备选方案,以避免导致堆栈溢出的无限循环。我的问题中发布的代码的工作版本如下:

    #r @"..\packages\FParsec\lib\net40-client\FParsecCS.dll"
    #r @"..\packages\FParsec\lib\net40-client\FParsec.dll"
    open FParsec
    
    type Parser<'t> = Parser<'t, unit>
    
    type Element =
        | Alternates of Element list
        | SequenceGroup of Element list
        | ParsedString of string
    
    let (pRuleElement, pRuleElementRef) : (Parser<Element> * Parser<Element> ref) = createParserForwardedToRef()
    let (pNotAlternatives, pNotAlternativesRef) : (Parser<Element> * Parser<Element> ref) = createParserForwardedToRef()
    
    let pString =
        pchar '"' >>. manyCharsTill (noneOf ['"']) (pchar '"')
        |>> ParsedString
    
    let pAlternates : Parser<_> =
        pipe2
            (pNotAlternatives .>>? (many (pchar ' ') >>? (pchar '/') >>. many (pchar ' ')))
            (sepBy1 pNotAlternatives (many (pchar ' ') >>? (pchar '/') >>. many (pchar ' ') ))
            (fun first rest -> first :: rest)
        |>> Alternates
    
    let pSequenceGroup : Parser<_> =
        between (pchar '(') (pchar ')') (sepBy1 pRuleElement (pchar ' '))
        |>> SequenceGroup
    
    do pRuleElementRef :=
        choice
            [
                pAlternates
                pSequenceGroup
                pString
            ]
    
    do pNotAlternativesRef :=
        choice
            [
                pSequenceGroup
                pString
            ]
    
    "\"0\" / (\"1\" \"2\") / \"3\" / (\"4\" / \"5\") / \"6\" / \"7\""
    |> run (pRuleElement .>> (skipNewline <|> eof))
    

    除了添加pNotAlternatives之外,我还对其进行了修改,以便在无法解析替代分隔符/时回溯,这允许它在“意识到”它不是替代列表之后继续全部。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-03-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-12-10
      相关资源
      最近更新 更多