使用 FParsec 解析箭头类型

Parsing the arrow type with FParsec

我正在尝试使用 FParsec 解析箭头类型。 也就是这个:

Int -> Int -> Int -> Float -> Char

例如

我尝试使用此代码,但它仅适用于一种类型的箭头 (Int -> Int),仅此而已。我也想避免使用括号,因为我已经有一个使用它们的元组类型,而且我也不希望它在语法方面太重。

let ws = pspaces >>. many pspaces |>> (fun _ -> ())

let str_ws s = pstring s .>> ws

type Type = ArrowType of Type * Type

let arrowtype' =
    pipe2
        (ws >>. ty')
        (ws >>. str_ws "->" >>. ws >>. ty')
        (fun t1 t2 -> ArrowType(t1, t2))

let arrowtype =
    pipe2
        (ws >>. ty' <|> arrowtype')
        (ws >>. str_ws "->" >>. ws >>. ty' <|> arrowtype')
        (fun t1 t2 -> ArrowType(t1, t2)) <?> "arrow type"

ty' 只是另一种类型,例如元组或标识符。

你有解决办法吗?

在进入箭头语法之前,我想对您的 ws 解析器发表评论。使用 |>> (fun _ -> ()) 有点低效,因为 FParsec 必须构造一个结果对象,然后立即将其丢弃。内置的 spaces and spaces1 解析器可能更适合您的需求,因为它们不需要构建结果对象。

现在,关于您正在努力解决的问题,在我看来,您希望以稍微不同的方式考虑箭头解析器。将其视为由 -> 分隔的一系列类型并使用 sepBy 系列解析器组合器怎么样?像这样:

let arrow = spaces1 >>. pstring "->" .>> spaces1
let arrowlist = sepBy1 ty' arrow
let arrowtype = arrowlist |>> (fun types ->
    types |> List.reduce (fun ty1 ty2 -> ArrowType(ty1, ty2))

请注意,arrowlist 解析器 也会 匹配普通的 Int,因为 sepBy1 的定义不是 "there must be at least one list separator",而是 "there must be at least one item in the list"。因此,要区分 Int 类型和箭头类型,您需要执行以下操作:

let typeAlone = ty' .>> notFollowedBy arrow
let typeOrArrow = attempt typeAlone <|> arrowtype

此处必须使用 attempt,这样如果出现箭头,ty' 消耗的字符将被回溯。

有一个复杂的因素我根本没有解决,因为你提到不需要括号。但是,如果您决定希望能够拥有箭头类型 of arrow types (即,将函数作为输入的函数),则需要解析类型 (Int -> Int) -> (Int -> Float) -> Char.这会使 sepBy 的使用复杂化,我根本没有解决它。如果您最终需要更复杂的解析,包括括号,那么您可能想要使用 OperatorPrecedenceParser。但是对于不涉及括号的简单需求,sepBy1 看起来是最好的选择。

最后,我应该给出一个警告:我根本没有测试过这个,只是把它输入到 Stack Overflow 框中。我给你的代码示例并不是为了按原样工作,而是为了让你了解如何继续。如果您需要一个按原样工作的示例,我很乐意为您提供一个,但我现在没有时间这样做。