Schemy Lisp 在 DOS 中
摘要
SLED 是一个受 Scheme 启发的 LISP 解释器,设计用于在 DOS 系统和模拟器上运行,具有不包含数值类型的符号计算功能。
暂无内容
查看缓存全文
缓存时间: 2026/09/07 21:35
# SLED - DOS上的Schemy LISP
来源:https://sled.neocities.org/
## 关于SLED
**(S**chemy **L**isp **e**n **D**OS) 是一个纯符号化的 **LISP**(列表处理语言),其功能(在很大程度上)受到 **Scheme** 的启发。它最初衍生自出色的Kilo LISP (https://t3x.org/klisp),但精简了一些功能(如宏),并增加了其他特性。SLED可以归类为一种原始的LISP,可在 **DOS**(磁盘操作系统)上运行,例如FreeDOS (https://freedos.org/) 或 MS-DOS,也可在DOS模拟器上运行,如DOSBox (https://dosbox.com/)、DOSBox-X (https://dosbox-x.com/) 或DOSBox-Staging (https://www.dosbox-staging.org/)。
关于提供的符号、特殊形式、内置函数和标准库的概览,请参阅索引 (https://sled.neocities.org/#index)。
获取SLED:
- 发布版本下载(包含编译好的二进制文件)(https://codeberg.org/gramian/sled/releases/download/v0.4/SLED-0_4.ZIP)
- 源代码仓库 (https://codeberg.org/gramian/sled)
- 备份仓库 (https://github.com/gramian/sled)
总的来说,SLED是一个用于DOS的LISP。
## 数据
有两种基本数据类型:**序对 (Pairs)** 和 **原子 (Atoms)** (非序对)。原子有三种变体:**符号 (Symbols)**、**闭包 (Closures)**(函数)以及一些**特殊符号 (Special Symbols)**。
### 符号
**符号**是唯一的名称,由以下字符的最多16个字符的任意组合构成:
```
a b c d e f g h i j k l m n o p q r s t u v w x y z
1 2 3 4 5 6 7 8 9 0 - . ? _
```
其中`.`不能是首字符。此外,任何可打印的ASCII字符在前面加上转义字符`\`(反斜杠)后,都可以成为符号的一部分,但`\(`、`\)`、`'`和`$`除外。
```
This\ is\ a\ sym\!
```
此外,除非字符被转义,否则大写字母会被接受但转换为小写。
### 特殊符号
SLED管理着一些预定义的特殊符号,例如表示“空列表”的`nil`。详情请参阅索引 (https://sled.neocities.org/#index)。
#### 引号
引号意味着“不要求值”。通过`quote`特殊形式,一个符号被注册:
```
(quote sym)
```
为方便起见,可以使用`'`简写形式:
```
'sym
```
本质上,引号将某些东西声明为数据而非代码。
### 序对
序对由一个**头部 (head)** 和一个**尾部 (tail)** 组成,每个部分持有一个`原子`或另一个`序对`。可以使用`.`(点)作为数据创建序对:
```
'(a . x)
```
或作为`cons`内置函数的结果:
```
(cons 'a 'x)
```
序对的元素(头部和尾部)是不可变的。
### 列表
列表是序对的序列,其中每个尾部指向另一个不同的序对,除了最后一个(它的尾部是`nil`值,等价于`()`)。以下是一些列表:
```
nil
'()
'(a . nil)
'(a . (b . nil))
```
列表也可以通过以下方式作为数据创建:
```
'(a b)
```
或作为(标准库)`list`函数的结果:
```
(list 'a 'b)
```
#### 非规范列表
非规范列表不以`nil`结尾,例如:
```
(a . (b . (c . d)))
```
#### 关联列表
关联列表是一个列表,其中每个元素都是一个序对(关联):
```
((a . x) (b . y) (c . z))
```
这种序对元素的头部称为**键 (key)**,尾部称为**值 (value)**。
### S-表达式
符号表达式(S-表达式)定义如下:一个S-表达式要么是一个原子,要么是一个S-表达式的序对。在Lisp、Scheme,特别是在SLED中,S-表达式既用于数据也用于源代码。
### 数字
SLED系统不包含数字类型。然而,自然数(非负整数)可以使用列表模拟:
```
'() ; 零
'(nil) ; 一
'(nil nil) ; 二
'(nil nil nil) ; 三
```
这些是计数符,因此基数代表量级,类似于冯·诺依曼序数。函数`inc`、`dec`和`zero?`有助于计数任务。
## 代码
在LISP中,未加引号的数据会被作为代码求值。
### 表达式
表达式可以被求值,例如:
- 绑定 (Bindings)
- 函数 (Functions)
- 特殊形式 (Special Forms)
### 绑定
绑定将一个符号链接到某些数据负载,通过`define`特殊形式创建:
```
(define a 'x)
```
### 闭包
**闭包**是函数及其环境的组合,由`lambda`特殊形式产生:
```
(define fun (lambda (arg1 arg2) (print arg1) (print arg2)))
```
### 函数应用
未加引号的列表的第一个元素被解释为一个求值后得到函数的表达式,其余元素作为该函数的参数:
```
(fun arg1 arg2)
```
函数的求值是立即的;因此,首先对参数表达式求值,然后使用已求值的参数进行函数应用。
### 参数
参数被求值后作为值的列表传递给函数。这意味着函数参数可以以多种方式设置:
```
(lambda x ...) ; x 是一个列表
(lambda (x y) ...) ; 解构列表,包含元素 x 和 y
(lambda (x y . z) ...) ; z 是一个列表(默认为 nil)
```
可选参数可以作为列表传递,如上面的`z`。
### 递归
LISP依赖递归而非迭代。递归是指函数调用自身。SLED的两个特性有助于避免深度递归时的栈溢出:蹦床求值器和尾调用优化 (TCO)。TCO适用于`lambda`、`let`、`begin`、`if`、`ifnil`和`apply`。此外,如果递归发生在第二个参数中,TCO也适用于`cons`。
### 错误
在对表达式求值期间发生错误时,会跳回顶层,在那里可以测试错误的发生。在表达式内部无法捕获错误。
### 内置函数
一组函数被内置到SLED可执行文件中,以实现与系统的交互和核心功能,详情请参阅索引 (https://sled.neocities.org/#index)。
### 标准库
除了核心函数外,一组典型的函数作为标准库实现在文件`sled.scm`中。详情请参阅索引 (https://sled.neocities.org/#index)。标准库可以用额外的自定义定义进行扩展。
### 特殊形式
某些形式看起来像函数,但不是。这些所谓的特殊形式不遵循函数行为,但使用与函数相同的语法。例如,`if`在解析形式之前不会对其参数求值。详情请参阅索引 (https://sled.neocities.org/#index)。
### 不可变性
在SLED中,特殊符号、特殊形式、内置函数和标准库内容是不可变的。此外,特殊形式和内置函数不能被遮蔽。标准库和相邻的自定义定义不能被重新定义。
## 系统
这个LISP系统是一个DOS应用程序。
### 文件名
文件名应遵循DOS 8.3命名规则(文件名最多8个字符,一个点,文件扩展名最多3个字符)。推荐在此LISP上运行的脚本的文件扩展名是`.scm`,因为其与`Scheme`在语法上相似;例如,标准库命名为`sled.scm`。但是,解释器不会检查文件扩展名。
### 启动
`sled`执行的第一个动作是加载其标准库,该库必须命名为`sled.scm`,并且预期位于与`SLED.EXE`解释器可执行文件相同的目录中。从标准库加载的所有符号及其值都变得不可变。
### 命令行参数
`sled`二进制文件有四个互斥的命令行参数。第一个只是显示帮助页面:
```
C:\> sled /?
```
第二个是要在REPL启动前加载的Lisp源文件路径,但在标准库加载之后:
```
C:\> sled code.scm
```
第三个是“批处理模式”开关`/B`,执行后退出:
```
C:\> sled /B code.scm
```
第四个是“忽略错误”开关`/I`,行为类似`/B`但在发生错误后继续执行:
```
C:\> sled /I code.scm
```
文件路径必须是最后一个参数。
### 文件路径
在解释器中,如果路径包含反斜杠,则需要转义,因为路径变成了一个符号,而单独的反斜杠`\`不是允许的符号字符。
```
C:\> sled to\my\code.scm
```
```
(load 'to\\my\\code.scm)
```
文件路径也受16个字符的限制。
### REPL
一旦`sled`启动,读取-求值-打印循环 (REPL) 就开始,并出现提示符:
```
sled>
```
它读取输入、求值、打印结果,并再次提示。现在开始使用吧:
```
sled> (println 'hello _ 'world)
```
#### 扩展字符
一个陷阱是不支持的扩展(双字节)字符。例如,在REPL中使用方向键会导致标准输入回显中出现`α`(alpha)。这些扩展字符会污染输入流,即使删除了也可能导致输入行出错。
### 退出
有两种常规方式退出`sled`。第一种是在顶层使用美元符号`$`,它告诉解析器退出:
```
sled> $
```
第二种是内置函数`\(exit\)`,求值后退出:
```
(exit)
```
### 注释
注释会被解析器忽略。注释由分号引入:
```
;
```
解析器会忽略到下一个换行符为止的所有字符。传统上,连续分号的数量传达语义,类似于Markdown标题:
```
;;;; 主标题
;;; 节标题
;; 行首
; 行尾
```
此外,块注释通过名为`comment`的特殊形式实现:
```
(comment ...)
```
注释形式内的括号必须平衡:
```
(comment ()) ; OK
(comment () ; → 错误
```
请注意,注释形式不能加引号:
```
'(comment test) ; → 错误
```
### 中断
要中断纯计算,使用`CTRL+Break`;要中断输入,可使用`CTRL+C`。
### 帮助
在REPL中,符号`?`可用于列出特殊形式、内置函数和标准库符号。
### 限制
作为实模式DOS程序,SLED有多个限制:
- 堆有12288个节点
- 符号表有2048个字符
标准库消耗了大约5%的节点和字符。
## 索引
**特殊符号**
**特殊形式**
**内置函数**
`apply` (https://sled.neocities.org/#apply)
`atom?` (https://sled.neocities.org/#atom)
`cons` (https://sled.neocities.org/#cons)
`defined?` (https://sled.neocities.org/#defined)
`empty?` (https://sled.neocities.org/#empty)
`env` (https://sled.neocities.org/#env)
`eof?` (https://sled.neocities.org/#eof)
`equiv?` (https://sled.neocities.org/#equiv)
`error` (https://sled.neocities.org/#error)
`exit` (https://sled.neocities.org/#exit)
`gc` (https://sled.neocities.org/#gc)
`head` (https://sled.neocities.org/#head)
`load` (https://sled.neocities.org/#load)
`newline` (https://sled.neocities.org/#newline)
`print` (https://sled.neocities.org/#print)
`proc?` (https://sled.neocities.org/#proc)
`read` (https://sled.neocities.org/#read)
`restart` (https://sled.neocities.org/#restart)
`symbol?` (https://sled.neocities.org/#symbol)
`tail` (https://sled.neocities.org/#tail)
`value` (https://sled.neocities.org/#value)
**标准别名**
**标准库**
`and?` (https://sled.neocities.org/#and)
`append` (https://sled.neocities.org/#append)
`assert` (https://sled.neocities.org/#assert)
`compose` (https://sled.neocities.org/#compose)
`dec` (https://sled.neocities.org/#dec)
`equal?` (https://sled.neocities.org/#equal)
`error?` (https://sled.neocities.org/#error-1)
`get` (https://sled.neocities.org/#get)
`id` (https://sled.neocities.org/#id)
`inc` (https://sled.neocities.org/#inc)
`list` (https://sled.neocities.org/#list)
`list?` (https://sled.neocities.org/#list-1)
`map` (https://sled.neocities.org/#map)
`member` (https://sled.neocities.org/#member)
`or?` (https://sled.neocities.org/#or)
`pair?` (https://sled.neocities.org/#pair)
`printid` (https://sled.neocities.org/#printid)
`println` (https://sled.neocities.org/#println)
`put` (https://sled.neocities.org/#put)
`reverse` (https://sled.neocities.org/#reverse)
`shorter?` (https://sled.neocities.org/#shorter)
---
### `$` {exit}
这个**特殊符号**退出REPL。它**不是**`\(exit\)`的简写形式,因为它是由解析器解析的。仅在提示符下有效。
---
### `?` {help}
这个**特殊符号**包含特殊形式、内置函数和标准库的概览。仅在提示符下有效。
---
### `'` {quote}
这个**特殊符号**是`quote`特殊形式的别名。
---
### `_` {space}
这个**标准别名**代表空格字符`'\\`。
---
### `and? `
这个**标准库**二元谓词回答两个参数是否都不是`nil`;用于简化复合条件。
```
(and? nil nil) ; → nil
(and? nil true) ; → nil
(and? true nil) ; → nil
(and? true true) ; → true
```
> **注意:**与Scheme不同,这不是一个具有可变参数的特殊形式(参数按顺序求值直到出现假结果),而是一个对两个参数都求值的二元函数。
---
### `ans`
这个**特殊符号**包含最后一个产生值的顶层形式的结果;在发生错误的情况下,`err`符号被设置,可以用`error?`来测试。
```
ans
```
---
### `append `
这个**标准库**二元函数返回一个列表,该列表由第二个参数列表连接到第一个参数列表的末尾组成。
```
(append nil (list 'a)) ; → (a)
(append (list 'a) nil) ; → (a)
(append (list 'a) (list 'b)) ; → (a b)
(append '(a b) '(c d)) ; → (a b c d)
(append '(a) 'b) ; → (a . b)
```
---
### `apply `
这个**内置**二元函数对第一个参数(函数)使用第二个参数列表作为参数进行求值。
```
(apply list '(a b c)) ; → (a b c)
```
---
### `assert `
这个**标准库**二元过程打印第二个参数,并在第一个参数求值为`nil`时引发错误。
```
(assert nil 'list\ empty)
```
---
### `atom? `
这个**内置**一元谓词回答参数是否为原子。
```
(atom? nil) ; → true
(atom? true) ; → true
(atom? '(a)) ; → nil
```
---
### `begin ... `
这个**特殊形式**按顺序对其参数求值,并返回最后一个参数的返回值。
```
(begin (print 'a) (print 'b) 'c) ; → c
(begin) ; → nil
```
---
### `br`
这个**标准别名**是`newline`。
---
### `comment ... `
这个**特殊形式**不被求值。用作块注释。与其他特殊形式不同,它在求值前由解码器解析。
```
(comment this :-D is ignored)
```
> **注意:**注释中的括号必须平衡。
---
### `compose ... `
这个**标准库**可变函数将一元函数管道化:第二个参数应用于第一个参数,第三个参数应用于前一个返回值,依此类推;最终参数的返回值作为结果返回。
```
(compose nil inc inc) ; → (nil nil)
```
---
### `cons `
这个**内置**二元函数返回一个序对,第一个参数作为头部,第二个参数作为尾部。
```
(cons 'a 'b) ; → (a . b)
```
---
### `dec `
这个**标准库**一元函数返回列表的尾部(如果非空);用于递减冯·诺依曼计数数。
```
(dec '(nil . nil)) ; → nil
```
---
### `define `
这个**特殊形式**创建第二个参数到第一个参数符号的新绑定,并返回第二个参数。绑定可以被重新**定义**,特殊符号、特殊形式、内置函数或标准库符号除外。
```
(define hello 'world) ; → world
```
> **注意:**`define`总是影响全局绑定,即使在`let`或`lambda`内部使用也是如此。
---
### `defined? `
这个**内置**一元谓词回答参数符号是否已在全局作用域中定义。
```
(defined? 'defined?) ; → true
(defined? 'undefined) ; → nil
```
---
### `empty? `
这个**内置**一元谓词回答其参数是否为空列表。
```
(empty? nil) ; → true
```
---
### `env`
这个**内置**零参过程打印当前用户定义的符号。
```
(env)
```
---
### `eof? `
这个**内置**一元谓词回答其参数是否求值为文件结束 (EOF) 或传输结束 (EOT) 符号。
```
(ifnil (eof? (read)) 'none)
```
---
### `equiv? `
这个**内置**二元谓词回答两个参数是否求值为相同的原子。
```
(equiv? 1 1) ; → true
(equiv? 'a 'b) ; → nil
```
---
### `error `
这个**内置**一元过程打印其参数并引发错误。
```
(error 'mistake)
```
---
### `error? `
这个**内置**零参谓词回答上次求值是否发生错误。
```
(error?) ; → true 或 nil
```
---
### `eval `
这个**内置**一元函数对其参数求值,然后对结果求值(两次求值)。
```
(eval ''hello) ; → hello
```
---
### `even? `
这个**标准库**一元谓词回答参数是否为偶数(使用计数符)。
```
(even? '(nil nil)) ; → true
```
---
### `exit`
这个**内置**零参过程退出解释器。
```
(exit)
```
---
### `filter `
这个**标准库**二元函数返回第一个参数列表中使第二个参数谓词为真的元素组成的列表。
```
(filter '(1 2 3) even?) ; → (2)
```
---
### `first `
这个**标准库**一元函数返回列表的第一个元素。
```
(first '(a b c)) ; → a
```
---
### `foldl `
这个**标准库**三元函数将二元函数从左到右归约列表。
```
(foldl + 0 '(1 2 3)) ; → 6
```
---
### `foldr `
这个**标准库**三元函数将二元函数从右到左归约列表。
```
(foldr cons '() '(a b c)) ; → (a b c)
```
---
### `gc `
这个**内置**零参过程强制进行垃圾回收。
```
(gc)
```
---
### `get `
这个**标准库**二元函数返回关联列表中键对应的值。
```
(get '((a 1) (b 2)) 'b) ; → 2
```
---
### `head `
这个**内置**一元函数返回序对的头部。
```
(head '(a . b)) ; → a
```
---
### `id `
这个**标准库**一元函数返回其参数(恒等函数)。
```
(id 'x) ; → x
```
---
### `if `
这个**特殊形式**对其第一个参数求值,如果为真则求值第二个参数,否则求值第三个参数。
```
(if true 'yes 'no) ; → yes
```
---
### `ifnil `
这个**特殊形式**对其第一个参数求值,如果为`nil`则求值第二个参数,否则求值第三个参数。
```
(ifnil nil 'yes 'no) ; → yes
```
---
### `inc `
这个**标准库**一元函数返回列表的头部(如果非空);用于递增冯·诺依曼计数数。
```
(inc '(nil . nil)) ; → (nil nil)
```
---
### `input `
这个**标准库**零参函数从标准输入读取一行并返回一个符号。
```
(input)
```
---
### `last `
这个**标准库**一元函数返回列表的最后一个元素。
```
(last '(a b c)) ; → c
```
---
### `let ... `
这个**特殊形式**按顺序对其参数求值,并将值绑定到符号,然后对体表达式求值。
```
(let (x 1) x) ; → 1
```
---
### `list `
这个**标准库**可变函数返回其参数组成的列表。
```
(list 'a 'b) ; → (a b)
```
---
### `list? `
这个**标准库**一元谓词回答参数是否为列表。
```
(list? '(a b)) ; → true
```
---
### `load `
这个**内置**一元函数从文件加载LISP代码。
```
(load 'code.scm)
```
---
### `map `
这个**标准库**二元函数将函数应用于列表的每个元素,并返回结果列表。
```
(map inc '(1 2 3)) ; → (2 3 4)
```
---
### `member `
这个**标准库**二元谓词回答第二个参数是否出现在第一个参数列表中。
```
(member 'b '(a b c)) ; → true
```
---
### `newline `
这个**内置**零参函数打印换行符。
```
(newline)
```
---
### `nil`
这个**特殊符号**表示空列表。
---
### `not `
这个**标准库**一元谓词对其参数的真值取反。
```
(not true) ; → nil
```
---
### `odd? `
这个**标准库**一元谓词回答参数是否为奇数(使用计数符)。
```
(odd? '(nil)) ; → true
```
---
### `or? `
这个**标准库**二元谓词回答两个参数是否至少有一个不是`nil`;用于简化复合条件。
```
(or? nil nil) ; → nil
(or? nil true) ; → true
(or? true nil) ; → true
(or? true true) ; → true
```
> **注意:**与Scheme不同,这不是一个具有可变参数的特殊形式(参数按顺序求值直到出现真结果),而是一个对两个参数都求值的二元函数。
---
### `pair? `
这个**标准库**一元谓词回答参数是否为序对。
```
(pair? '(a . b)) ; → true
```
---
### `peek `
这个**标准库**零参函数从标准输入读取下一个字符而不移除它。
```
(peek)
```
---
### `print `
这个**内置**一元函数打印其参数。
```
(print 'hello)
```
---
### `printid `
这个**标准库**一元函数打印其参数及其标识符。
```
(printid 'x)
```
---
### `println `
这个**标准库**可变函数打印其参数并换行。
```
(println 'hello _ 'world)
```
---
### `proc? `
这个**内置**一元谓词回答参数是否为过程(函数)。
```
(proc? car) ; → true
```
---
### `put `
这个**标准库**三元函数向关联列表添加一个新的键值对。
```
(put '((a 1)) 'b 2) ; → ((a 1) (b 2))
```
---
### `quote `
这个**特殊形式**返回其参数而不求值。
```
(quote x) ; → x
```
---
### `read `
这个**内置**零参函数从标准输入读取并返回一个S-表达式。
```
(read)
```
---
### `rest `
这个**标准库**一元函数返回列表的尾部(除第一个元素外的所有元素)。
```
(rest '(a b c)) ; → (b c)
```
---
### `reverse `
这个**标准库**一元函数返回其参数的逆序列表。
```
(reverse '(a b c)) ; → (c b a)
```
---
### `set! `
这个**特殊形式**重新绑定一个已存在的符号到新值。
```
(set! x 1)
```
---
### `shorter? `
这个**标准库**二元谓词回答第一个参数列表是否比第二个参数列表短。
```
(shorter? '(a) '(a b)) ; → true
```
---
### `tail `
这个**内置**一元函数返回序对的尾部。
```
(tail '(a . b)) ; → b
```
---
### `true`
这个**特殊符号**表示真值。
---
### `until `
这个**标准库**二元函数重复执行第一个参数(零参函数),直到第二个参数谓词为真。
```
(until (lambda () (print 1)) false)
```
---
### `value `
这个**内置**一元函数返回其参数的值。
```
(value 'x)
```
---
### `when `
这个**特殊形式**对其第一个参数求值,如果为真则对其第二个参数求值。
```
(when true (print 1))
```
---
### `while `
这个**标准库**二元函数重复执行第一个参数(零参函数),只要第二个参数谓词为真。
```
(while true (print 1))
```
---
### `with-input-file `
这个**标准库**三元函数使用文件作为标准输入执行其第一个参数(零参函数),第二个参数是文件名,第三个参数是模式(`'r`用于读取)。
```
(with-input-file (lambda () (read)) 'data.txt 'r)
```
---
### `write `
这个**标准库**一元函数将其参数写入标准输出。
```
(write 'x)
```
---
### `yield `
这个**标准库**零参函数从生成器产生一个值。
```
(yield 1)
```
---
### `zero? `
这个**标准库**一元谓词回答参数是否为零(计数符)。
```
(zero? '()) ; → true
```
相似文章
scScript Linux版
scScript是一种类似C的脚本语言,它编译为字节码并在Linux的虚拟机上运行,与scEmacs编辑器集成。
Slisp: 简单的Lisp编译器 (Linux/amd64)
Slisp 是一个简单的编译器,它读取 Lisp 程序并生成适用于 Linux/AMD64 的独立汇编表示,支持基本原语、闭包和标准库。
Smolts:专为教学语言设计的教学IDE(eighty-twenty news)
Smolts是一款面向SMoL语言的教学IDE,支持用户编写代码并以可视化方式单步调试,展示续延、环境和存储等概念。
在浏览器中试用 LispE
关于在浏览器中试用 LispE(一种 Lisp 方言)的简要介绍或链接。
Jolt 的程序镜像与可移植 Scheme 后端
Jolt 是一种 Lisp/Scheme 语言,最近新增了两项功能:类似 Common Lisp 和 Smalltalk 风格的程序镜像,用于完整状态序列化与调试;以及一个与 Chez 运行时解耦的可移植 Scheme 后端,实现与架构无关的可移植性。