如何在 FParsec 中解析 recusrive 语法

How to parse recusrive grammar in FParsec

提问人:Chechy Levas 提问时间:3/3/2022 更新时间:3/3/2022 访问量:138

问:

以前的问题,我无法用它来让它工作

FParsec 中的递归语法

  • 似乎是一个在添加到FParsec之前被问到的老问题createParserForwardedToRef
  • AST 似乎不像我的那么可怕的递归。

解析为递归数据结构

  • 语法依赖于特殊字符来指示另一个嵌套级别。我没有这种奢侈'['

我想为我最近发现自己在写的语言构建一种 Lexer 和项目系统。该语言称为 q。它是一种相当简单的语言,没有运算符优先级。例如,与 相同。它的工作原理有点像反向抛光符号计算器,评估是从右到左的。1*2+3(1*(2+3))

我在 FParsec 中表达这一点时遇到麻烦。我整理了以下简化的演示

open FParsec

type BinaryOperator = BinaryOperator of string
type Number = Number of string

type Element =
  |Number of Number
and Expression = 
  |Element of Element
  |BinaryExpression of Element * BinaryOperator * Expression

let number = regex "\d+\.?\d*" |>> Number.Number

let element = [ number ] |> choice |>> Element.Number

let binaryOperator = ["+"; "-"; "*"; "%"] |> Seq.map pstring |> choice |>> BinaryOperator

let binaryExpression expression = pipe3 element binaryOperator expression (fun l o r -> (l,o,r))
let expression =

  let exprDummy, expRef = createParserForwardedToRef()
  let elemExpr = element |>> Element
  let binExpr = binaryExpression exprDummy |>> BinaryExpression
  expRef.Value <- [binExpr; elemExpr; ] |> choice
  expRef

let statement = expression.Value .>> eof

let parseString s =
  printfn "Parsing input: '%s'" s

  match run statement s with
  | Success(result, _, _)   -> printfn "Ok: %A" result
  | Failure(errorMsg, _, _) -> printfn "Error: %A" errorMsg

//tests
parseString "1.23"
parseString "1+1"
parseString "1*2+3" // equivalent to (1*(2+3))

到目前为止,我还没有想出一种方法来满足所有 3 个测试用例。在上面,它首先尝试解析,意识到它不能,但随后必须消耗输入,因为它不会尝试接下来进行评估。不知道该怎么办。如何满足 3 项测试?binExprelemExpr

f# fparsec

评论

0赞 Good Night Nerd Pride 3/4/2022
您看到的错误到底是什么?
0赞 Good Night Nerd Pride 3/4/2022
你试过FParsec的运算符优先级解析器吗?即使你没有优先权,它仍然使你的情况变得容易得多。除非你是为了学习目的而编写的。
0赞 Chechy Levas 3/4/2022
我没有尝试过运算符优先级解析器。会看一看。此外,这不仅仅是为了学习目的。我倾向于使用最简单的代码。

答:

3赞 Chechy Levas 3/3/2022 #1

沉思着托马斯的回答,我想出了以下可行的方法

let expr, expRef = createParserForwardedToRef()
let binRightExpr = binaryOperator .>>. expr
expRef.Value <- parse{
  let! first = element
  return! choice [
      binRightExpr |>> (fun (o, r) -> (first, o, r) |> BinaryExpression)
      preturn (first |> Element)
    ]
}

let statement = expRef.Value .>> eof

FParsec 文档中给出了第一个解析器失败的原因

<|> 组合器的行为有两个重要特征:

<|> 仅在左侧的解析器在左侧时尝试右侧的解析器 侧失败。它不实现最长匹配规则。 但是,仅当左解析器失败而不消耗输入时,它才会尝试右解析器。

可能需要清理一些东西,比如 AST 的结构,但我认为我很好。