递归解析语法消耗输入且无法解析序列

时间:2019-06-10 05:02:01

标签: parsing recursion f# fparsec

我正在尝试编写一个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异常终止,因为它随后尝试重复解析替代项。

2 个答案:

答案 0 :(得分:2)

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

您可以在主要输入上运行pAlternates解析器,该解析器实际上是有效的:

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

我怀疑您可能可以做到这一点-pAlternates解析器即使在单个字符串上也可以正常工作-它将仅返回包含一个单例列表的Alternates

答案 1 :(得分:0)

该解决方案似乎只是在解析备选方案时根本不尝试解析备选方案,以避免无限循环导致栈溢出。我的问题中发布的代码的有效版本如下:

#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之外,我还对其进行了修改,以使其在无法解析替代分隔符/时回溯,从而使它在“意识到”不是毕竟是替代品列表。