目录
写一个解析器
解析器的实现是需要根据语言特性来实现的,是一个较为复杂的任务。事实上,我们需要将一段代码或者字母转换为抽象语法树 (abstract syntax tree, AST)。抽象语法树是程序在内存中展现的一种形式,抽象表示它不关心是由什么源码构成的,但是他很确信是符合语义学的。
例如:
sum = lambda(a, b) { a + b;}print(sum(1, 2));复制代码
解析器会将上面的代码转换为一个javascript对象
:
{ type: "prog", prog: [ // 第一行 { type: "assign", operator: "=", left: { type: "var", value: "sum" }, right: { type: "lambda", vars: [ "a", "b" ], body: { // body 部分也应该是 prog 类型,因为它包含一个表达式 type: "binary", operator: "+", left: { type: "var", value: "a" }, right: { type: "var", value: "b" } } } }, // 第二行 { type: "call", func: { type: "var", value: "print" }, args: [{ type: "call", func: { type: "var", value: "sum" }, args: [ { type: "num", value: 1 }, { type: "num", value: 2 } ] }] } ]}复制代码
写一个解析器最大的困难在于如何合理的组织代码。解析器应该站在比读取字符更高的层面。这里有一些建来控制程序的适度的复杂性:
- 写小而精的函数。每个函数只做一件事,并把它做好。
- 不要用正则表达式去解析。在写词法分析器的时候正则表达式很有用,但是建议尽量不要把它用在很简单的事情上
- 不要尝试去猜。当不确定解析什么的时候,抛出一个包含位置的错误,比如: 第2行25列错误
为了保持胆码的简洁性,我将代码分割成了三部分,将来会被分割成更小的函数:
- 字符输入流
- token输入流
- 解析器
原文链接: