解析递归数据结构

时间:2017-07-20 15:07:40

标签: f# fparsec

我希望使用F#将字符串解析为递归数据结构。在这个问题中,我将提出一个简化的例子,它将切入我想要做的事情的核心。

我想将一串嵌套的方括号解析为记录类型:

type Bracket = | Bracket of Bracket option

所以:

  • " []" - > Bracket None
  • " [[]]" - > Bracket ( Some ( Bracket None) )
  • " [[[]]]" - > Bracket ( Some ( Bracket ( Some ( Bracket None) ) ) )

我想使用FParsec库中的解析器组合器来完成此操作。以下是我到目前为止的情况:

let tryP parser = 
    parser |>> Some
    <|>
    preturn None

/// Parses up to nesting level of 3
let parseBrakets  : Parser<_> = 

let mostInnerLevelBracket = 
    pchar '[' 
    .>> pchar ']'
    |>> fun _ -> Bracket None

let secondLevelBracket = 
    pchar '['
    >>. tryP mostInnerLevelBracket
    .>> pchar ']'
    |>> Bracket

let firstLevelBracket = 
    pchar '['
    >>. tryP secondLevelBracket
    .>> pchar ']'
    |>> Bracket

firstLevelBracket

我甚至有一些Expecto测试:

open Expecto

[<Tests>]
let parserTests = 
[ "[]", Bracket None 
    "[[]]", Bracket (Some (Bracket None))
    "[[[]]]", Bracket ( Some  (Bracket (Some (Bracket None))))  ]
|> List.map(fun (str, expected) -> 
    str
    |> sprintf "Trying to parse %s"
    |> testCase
    <| fun _ -> 
    match run parseBrakets str with
    | Success (x, _,_) -> Expect.equal x expected "These should have been equal"
    | Failure (m, _,_) -> failwithf "Expected a match: %s" m
)
|> testList "Bracket tests"

let tests = 
[ parserTests ]
|> testList "Tests"

runTests defaultConfig tests

问题当然是如何处理和任意级别的嵌套 - 上面的代码最多只能用于3个级别。我要喜欢写的代码是:

let rec pNestedBracket = 
    pchar '['
    >>. tryP pNestedBracket
    .>> pchar ']'
    |>> Bracket

但是F#并不允许这样做。

我是否完全咆哮错误的树以及如何解决这个问题(我知道有更简单的方法可以解决这个问题)?

2 个答案:

答案 0 :(得分:5)

您正在寻找FParsecs createParserForwardedToRef方法。因为解析器是值而不是函数,所以为了做到这一点,不可能做出相互递归或自递归解析器,在某种意义上你必须在定义之前声明解析器。

您的最终代码最终会看起来像这样

let bracketParser, bracketParserRef = createParserForwardedToRef<Bracket>()
bracketParserRef := ... //here you can finally declare your parser
    //you can reference bracketParser which is a parser that uses the bracketParserRef

另外,我会推荐这篇文章,以便对解析器组合器有基本的了解。 https://fsharpforfunandprofit.com/posts/understanding-parser-combinators/。 JSON解析器的最后一节讨论了createParserForwardedToRef方法。

答案 1 :(得分:2)

作为如何使用createParserForwardedToRef的示例,这里是我最近写的一个小解析器的片段。它解析括号之间以空格分隔的整数列表(列表可以嵌套),以及&#34;整数&#34;可以是小型算术表达式,如1+23*5

type ListItem =
    | Int of int
    | List of ListItem list

let pexpr = // ... omitted for brevity

let plist,plistImpl = createParserForwardedToRef()

let pListContents = (many1 (plist |>> List .>> spaces)) <|>
                    (many  (pexpr |>> Int  .>> spaces))

plistImpl := pchar '[' >>. spaces
                       >>. pListContents
                       .>> pchar ']'

P.S。我会把这作为对Thomas Devries的答案的评论,但评论不能包含格式良好的代码。来吧接受他的回答;我的目的只是为了充实他。