【问题标题】:Parsing simple types in FParsec在 FParsec 中解析简单类型
【发布时间】:2011-07-20 15:00:09
【问题描述】:

我正在尝试使用 FParsec 解析标准的简单类型(在 lambda 演算的意义上),但是我很难从 Lex/Yacc 样式转换为 FParsec 中使用的样式,特别是在递归定义方面。

我尝试解析的类型示例如下:

  • o
  • o -> o
  • (o -> o -> o) -> o

这是我的尝试:


    type SType =
      | Atom
      | Arrow of SType * SType

    let ws = spaces
    let stype, styperef = createParserForwardedToRef()

    let atom = pchar 'o' .>> ws |>> (fun _ -> Atom)

    let arrow = pipe2 (stype .>> (pstring "->" .>> ws)) 
                      stype
                      (fun t1 t2 -> Arrow (t1,t2))

    let arr = parse {
                let! t1 = stype
                do!  ws
                let! _  = pstring "->"
                let! t2 = stype
                do!  ws
                return Arrow (t1,t2)
              }

    styperef := choice [ pchar '(' >>. stype .>> pchar ')';
                         arr;
                         atom ]

    let _ = run stype "o -> o"`

当我将它加载到交互中时,最后一行会导致堆栈溢出(具有讽刺意味的是,这些天很难搜索)。鉴于存在递归引用,我可以想象为什么,但我会认为一个标记前瞻会阻止遵循stype 中的第一个(括号中的)选择。因此我假设它必须选择arr,它选择stype,依此类推。但是如何防止这种循环呢?

我对 cme​​ts 关于库的惯用用法以及对我尝试的解决方案的更正感兴趣。

【问题讨论】:

标签: f# fparsec


【解决方案1】:

当您使用 FParsec 时,您需要借助 sequence combinators 而不是左递归来解析序列。例如,在您的情况下,您可以使用 sepBy1 组合器:

open FParsec

type SType =
     | Atom
     | Arrow of SType * SType

let ws = spaces : Parser<unit, unit>
let str_ws s = pstring s >>. ws

let stype, stypeRef = createParserForwardedToRef()

let atom = str_ws "o" >>% Atom

let elem = atom <|> between (str_ws "(") (str_ws ")") stype

do stypeRef:= sepBy1 elem (str_ws "->") 
              |>> List.reduceBack (fun t1 t2 -> Arrow(t1, t2))

let _ = run stype "o -> o"

【讨论】:

  • 我一直忘记 sepBy。不错的答案!
  • 非常感谢,就像使用 >>% 一样。然而,这并没有捕捉到“->”的右结合性。我将 stypeRef 的定义更改为chainr1 elem ((str_ws "-&gt;") &gt;&gt;% (fun t1 t2 -&gt; Arrow (t1,t2))),尽管您可能也可以使用List.reduce 的右关联版本。
  • 我已将 reduce 替换为 reduceBack 以将箭头解析为右关联运算符。我发现使用sepByreduceBack 的直接解决方案比chainr 实现更干净,特别是因为归约函数是一个常数。由于箭头是一个右结合运算符,你总是需要用序列的元素构建某种中间堆栈或列表,所以在这里使用chainr1也没有效率优势。相反,它应该会慢一些,因为它还需要记录解析的归约函数。
【解决方案2】:

这可以运行,但可能被黑客攻击太多了。 type Parser... 的内容来自 FParsec 文档,以避免编译器错误。

type SType = 
    | Atom 
    | Arrow of SType * SType

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


let ws = spaces

let atom : Parser<_> = pchar 'o' .>> ws |>> (fun _ -> Atom)

let rec term =
    parse {
        // Force something to come before another term.  Either
        // an atom, or a term in parens.
        let! first = choice [atom;
                             (pstring "(" .>> ws) >>. term .>> 
                              (pstring ")" .>> ws)]

        // Check if this is an arrow.  If not, just return first.
        let! res = choice [((pstring "->") .>> ws) >>. term |>> (fun x ->
                               Arrow (first, x));
                           preturn first]
        return res
        }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多