【感谢@李欲纯 的热心翻译。如果其他朋友也有不错的原创或译文,可以尝试推荐给伯乐在线。】
Little Lisp是一个解释器,支持函数调用、lambda表达式、 变量绑定(let)、数字、字符串、几个库函数和列表(list)。我写这个是为了在Hacker School(一所位于纽约的程序员培训学校)的一个闪电秀中展示写一个解释器不是很难。一共只有116行的JavaScript代码,下文我会解释它是如何运行的。
首先,让我们学习一些Lisp。
Lisp基础
这是一个原子,最简单的Lisp形式:
1 |
1 |
这是另一个原子,一个字符串:
1 |
"a" |
这是一个空列表:
()
这是一个包含了一个原子的列表:
1 |
(1) |
这是一个包含了两个原子的列表:
1 |
(1 2) |
这是一个包含了一个原子和另一个列表的列表:
1 |
(1 (2)) |
这是一个函数调用。函数调用由一个列表组成,列表的第一个元素是要调用的函数,其余的元素是函数的参数。函数first
接受一个参数(1 2)
,返回1
。
1 2 3 |
(first (1 2)) => 1 |
这是一个lambda表达式,即一个函数定义。这个函数接受一个参数x
,然后原样返回它。
1 2 |
(lambda (x) x) |
这是一个lambda调用。lambda调用由一个列表组成,列表的第一个元素是一个lambda表达式,其余的元素是由lambda表达式所定义的函数的参数。这个lambda表达式接受一个参数"lisp"
并返回它。
1 2 3 4 5 |
((lambda (x) x) "Lisp") => "Lisp" |
Little Lisp是如何运行的
写一个Lisp解释器真的很容易。
Little Lisp的代码包括两部分:分析器和解释器
分析器
分析分两个阶段:分词(tokenizing)和加括号(parenthesizing)。
tokenize()
接受一个Lisp字符串,在每个括号周围加上空格,然后用空格作为分隔符拆分整个字符串。举个例子,它接受((lambda (x) x) "Lisp")
,将它变换为( ( lambda ( x ) x ) "Lisp" )
,然后进一步变换为['(', '(', 'lambda', '(', 'x', ')', 'x', ')', '"Lisp"', ')']
。
1 2 3 4 5 6 |
var tokenize = function(input) { return replace(/\(/g, ' ( ') .replace(/\)/g, ' ) ') .trim() .split(/\s+/); }; |
parenthesize()
接受由tokenize()
产生的词元列表,生成一个嵌套的数组来模拟出Lisp代码的结构。在这个嵌套的数组中的每个原子会被标记为标识符或文字表达式。例如,['(', '(', 'lambda', '(', 'x', ')', 'x', ')', '"Lisp"', ')']
被变换为:
value: 'lambda' }, [{ type: 'identifier', value: 'x' }],
{ type: 'identifier', value: 'x' }],
{ type: 'literal', value: 'Lisp' }]
|