目录
写一个解析器
解析器的实现是需要根据语言特性来实现的,是一个较为复杂的任务。事实上,我们需要将一段代码或者字母转换为抽象语法树 (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输入流
- 解析器