7 个版本

0.3.3 2023年11月25日
0.3.2 2023年10月31日
0.3.2-1 2023年11月21日
0.2.1 2023年10月13日
0.1.0 2023年10月2日

#1378 in 解析器实现

Download history 34/week @ 2024-03-28 18/week @ 2024-04-04

74 每月下载量

MIT 许可证

120KB
3K SLoC

LISP I 解释器

coverage

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,但限制了作用域。它接受两个参数:nameexpression,并在该表达式的主体中将nameexpression相关联。

  • LAMBDA - 接受一个虚变量列表和一个表达式。当与与虚变量数量相同的参数评估时,它将它们替换在表达式内部,然后评估该表达式本身。

算术

  • SUM - 返回两个数字的和
  • PRDCT - 返回两个数字的乘积
  • EXPT - 返回第一个数字的第二次方,即第二个数字的幂

其他

  • NOT - 如果是F则返回T,如果是T则返回F
  • NULL - 如果是NIL或0则返回T,否则返回F
  • EQUAL - 比较两个表达式
  • 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