我们已经使用 ANTLR 为类似 SQL 的语法创建了一个解析器,虽然在大多数情况下结果令人满意,但我们需要修复一些边缘情况;由于我们没有自己编写解析器,因此我们对它的理解还不够深入,无法做出明智的更改。
所以,我们想编写自己的解析器。手动编写解析器的最佳方法是什么?我们应该使用什么样的解析器——推荐使用递归下降;那正确吗?我们将使用 C# 编写它,因此我们将不胜感激地收到任何使用该语言编写解析器的教程。
更新:我也对涉及 F# 的答案感兴趣 - 我一直在寻找在项目中使用它的理由。
我强烈推荐 F# 语言作为在 .NET 平台上进行解析的首选语言。它起源于 ML 语言家族,这意味着它对面向语言的编程提供了出色的支持。
有区别的联合和模式匹配允许对您的 AST 进行非常简洁和强大的规范。高阶函数允许定义解析操作及其组合。对 monadic 类型的一流支持允许隐式处理状态管理,从而大大简化了解析器的组合。强大的类型推断极大地帮助了这些(复杂)类型的定义。所有这些都可以交互方式指定和执行,让您可以快速制作原型(prototype)。
Stephan Tolksdorf 已通过他的解析器组合器库将其付诸实践 FParsec
从他的示例中我们可以看出 AST 是如何自然地指定的:
type expr =
| Val of string
| Int of int
| Float of float
| Decr of expr
type stmt =
| Assign of string * expr
| While of expr * stmt
| Seq of stmt list
| IfThen of expr * stmt
| IfThenElse of expr * stmt * stmt
| Print of expr
type prog = Prog of stmt list
解析器的实现(部分省略)同样简洁:
let stmt, stmtRef = createParserForwardedToRef()
let stmtList = sepBy1 stmt (ch ';')
let assign =
pipe2 id (str ":=" >>. expr) (fun id e -> Assign(id, e))
let print = str "print" >>. expr |>> Print
let pwhile =
pipe2 (str "while" >>. expr) (str "do" >>. stmt) (fun e s -> While(e, s))
let seq =
str "begin" >>. stmtList .>> str "end" |>> Seq
let ifthen =
pipe3 (str "if" >>. expr) (str "then" >>. stmt) (opt (str "else" >>. stmt))
(fun e s1 optS2 ->
match optS2 with
| None -> IfThen(e, s1)
| Some s2 -> IfThenElse(e, s1, s2))
do stmtRef:= choice [ifthen; pwhile; seq; print; assign]
let prog =
ws >>. stmtList .>> eof |>> Prog
在第二行,例如stmt
和 ch
是解析器和 sepBy1
是一个单子(monad)解析器组合器,它接受两个简单的解析器并返回一个组合解析器。在这种情况下 sepBy1 p sep
返回一个解析器,该解析器解析一次或多次出现的 p
用sep
分隔.因此,您可以看到从简单的解析器中组合出强大的解析器的速度有多快。 F# 对重写运算符的支持还允许使用简洁的中缀表示法,例如排序组合器和选择组合器可以指定为 >>.
和 <|>
.
祝你好运
丹尼