F# 解析FParsec中的简单类型

F# 解析FParsec中的简单类型,f#,fparsec,F#,Fparsec,我试图使用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 = createParserForw

我试图使用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"`
当我将其加载到interactive中时,最后一行导致堆栈溢出(讽刺的是,现在很难搜索)。考虑到存在递归引用,我可以想象为什么,但我本以为一个令牌前瞻会阻止遵循
stype
中的第一个(括号中的)选项。因此,我假设它必须选择
arr
,它选择
stype
,依此类推。但如何防止这种循环呢


我感兴趣的是对库的惯用用法的评论,以及对我尝试的解决方案的更正。

这是可行的,但可能是太多了。
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
        }
类型SType=
|原子
|SType的箭头*SType
类型UserState=unit
类型分析器
设ws=spaces
让atom:Parser=pchar'o.>>ws |>>(乐趣->atom)
让我们来看看这个术语=
解析{
//在下一学期之前强迫某件事发生
//一个原子,或帕伦斯中的一个术语。
让!第一=选择[原子;
(pstring)(“>>ws)>>.term.>>
(pstring“)”>>ws)]
//检查这是否是箭头。如果不是,请先返回。
let!res=choice[((pstring“->”)。>>ws)>>.term |>>(fun x->
箭头(第一,x));
先烧后烧]
返回res
}

当您使用FParsec时,您需要借助于而不是左递归来解析序列。例如,您可以使用
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"
打开FParsec
类型SType=
|原子
|SType的箭头*SType
设ws=spaces:Parser
让str_ws s=pstring s>>。ws
设stype,stypeRef=createParserForwardedToRef()
让atom=str_ws“o”>>%atom
设elem=str_-ws(“”(str_-ws”))stype之间的原子
do stypeRef:=sepBy1元素(str_ws“->”)
|>>List.reduceBack(乐趣t1 t2->箭头(t1,t2))
让我们运行stype“o->o”

这可能是您想要的:谢谢,我已经阅读了该问题/答复,但我不太明白如何继续回答我的问题。不过我会再看一眼的。我总是忘了塞比。回答得好!这非常感谢,就像使用>>%一样。但是,这并不能捕获“->”的正确关联性。我将stypeRef的定义更改为
chainr1元素((str_ws“->”>)
(fun t1 t2->Arrow(t1,t2)),不过您可能也可以使用
列表的右关联版本。reduce
。我将
reduce
替换为
reduceBack
,将箭头解析为右关联运算符。我发现使用
sebby
reduceBack
的简单解决方案比
chainr
实现更简洁,尤其是因为reduce函数是一个常量。由于箭头是一个右关联运算符,因此始终必须使用序列的元素构建某种中间堆栈或列表,因此在此处使用
chainr1
也没有效率优势。相反,它应该稍微慢一点,因为它还需要保存解析的缩减函数的记录。