7 个版本
0.3.3 | 2023年11月25日 |
---|---|
0.3.2 | 2023年10月31日 |
0.3.2-1 |
|
0.2.1 | 2023年10月13日 |
0.1.0 | 2023年10月2日 |
#1378 in 解析器实现
74 每月下载量
120KB
3K SLoC
LISP I 解释器
1960年,麻省理工学院AI研究团队保罗·麦卡锡的卓越才智偶然发现了一些非凡的事物,这永远改变了计算机世界。他在论文"符号表达式的递归函数及其机器计算"中记录了他的发现,其中他介绍了一种递归定义函数的形式化方法,强调了它作为编程语言和计算理论框架的潜力。它探讨了S表达式和S函数的概念,以及它们在实际应用中的实用性,如机械定理证明。由于其实现中大量使用了列表,因此被称为LISP(代表列表处理器)。
两年后,即1962年,一个附录,称为LISP I 程序员手册问世。它主要包含针对IBM704的详细函数列表,形成了我们现在所知的LISP I的核心。这种实现不仅仅是一个系统;它是一场即将展开的革命。
欢迎来到 lispi
,这是一种美妙语言的解释器。这个项目的目标是实现程序员手册中描述的所有函数。第147页的“函数字母索引”列出了总共90个函数,但我发现其中只有大约40个是针对用户的,其余的只是用于那些函数的定义,单独使用似乎没有用处。
本readme中的可用函数部分提供了当前项目状态,并且每次添加新函数时都会更新。它也可以作为LISP I函数的一些简单参考。
用法
使用 cargo install lispi
从crates.io安装后,您可以通过运行 lispi
访问REPL。在REPL中,您可以执行如下操作:
>> (define (fac
(lambda (n)
(cond
((equal n 0) 1)
(T (prdct n (fac (sum n -1))))))))
fac
>> (fac 5)
120
通过运行 lispi FILE.lisp
评估文件。它将顺序评估文件中的S表达式并打印它们的值。
值得注意的是,对于IBM704,它们使用了apply
作为“通用LISP函数”,这要求你在“行”的第一个符号中指定函数名,然后是参数,最后是一个关联列表,大多数情况下都是空的!因为这个用法相当不灵活,所以我决定坚持使用更现代的方法,其中每个程序的“行”都通过eval
进行评估。
可用函数
基本
ATOM
- 检查符号是否为原子,即不是列表EQ
- 比较原子CAR
- 获取列表的第一个元素CDR
- 获取列表的“尾部”,即除了第一个元素之外的所有内容- 任何CAR和CDR的(小于3个元素的)组合,例如
CADAR
CONS
- 构造一个包含两个元素的cons对象
特殊形式
-
QUOTE
- 返回其参数作为字面量 -
COND
- 接受(谓词表达式)对作为其参数,并返回匹配第一个返回T的谓词的表达式的值 -
AND
- 如果其任何参数是F,则返回F -
OR
- 如果其任何参数是T,则返回T -
DEFINE
- 接受对(symbol value)
,并将相应的值分配给每个符号。当符号被评估时,返回分配的值。DEFINE
返回最后一个定义的符号。 -
LABEL
- 类似于DEFINE
,但限制了作用域。它接受两个参数:name
和expression
,并在该表达式的主体中将name
与expression
相关联。 -
LAMBDA
- 接受一个虚变量列表和一个表达式。当与与虚变量数量相同的参数评估时,它将它们替换在表达式内部,然后评估该表达式本身。
算术
SUM
- 返回两个数字的和PRDCT
- 返回两个数字的乘积EXPT
- 返回第一个数字的第二次方,即第二个数字的幂
其他
NOT
- 如果是F则返回T,如果是T则返回FNULL
- 如果是NIL或0则返回T,否则返回FEQUAL
- 比较两个表达式LIST
- 根据提供的参数创建一个列表ERROR
- 允许程序提前退出TRACKLIST
- 将函数名作为参数,并将它们作为列表返回。任何时候评估了TRACKLIST
的函数,屏幕上都会打印出两条消息:首先是在评估之前,显示函数名和其参数,其次是评估之后,显示它返回的值。
实现
因为我使用的是rust,它已经是汇编的抽象,所以我不必与手册中显示的结构完全相同。这也许在某种程度上使得这个LISP实现更少像一个列表处理器。
符号
(参考程序员手册第88页)
而不是在内存中有一个位置,并关联一个所有符号的列表(这个列表反过来又会有自己的属性列表,例如内置函数的情况下的子例程指针),我只为内置符号使用了枚举,并在解析时创建了一个具有适当变体的AST。当使用eval
运行AST时,会执行仅仅是普通Rust函数的子例程。在运行时定义的符号存储在一个关联列表中,它们只与它们的值配对,当使用eval
时,会与它们的关联值交换。
副作用
目前唯一产生副作用的函数是DEFINE
,它将填充全局关联列表。我通过状态单子(kinda)来实现它。这是因为每次调用eval
之后,除了表达式的结果值之外,还会返回一个新的关联列表,该列表将在REPL中后续的eval
调用中使用。
许可协议
显然是MIT协议。可在LICENSE
文件中找到。
依赖关系
~6.5–8.5MB
~150K SLoC