minilisp项目介绍
项目地址:https://github.com/rui314/minilisp
作者也是rui314,commits也是按照模块开发提交的。
minilisp只有一个代码文件:https://github.com/rui314/minilisp/blob/master/minilisp.c
加注释也只有996行。
代码结构,测试方法与chibicc类似,但minilisp比chibicc早7年开发。
此工程刚好是An Incremental Approach to Compiler Construction 逐步构建编译器的方法 论文的具体实现,虽然不是一个作者。
lisp似乎是图灵机的最简化实现,minilisp似乎更是。
据说美国有些高校早年间先教lisp,后来才改为c。
因此,对于学习编译器,似乎从minilisp更容易入手,lisp语法也是比较简单。
Scheme Interpreter on web(Scheme在线解释器)
https://inst.eecs.berkeley.edu/~cs61a/fa14/assets/interpreter/scheme.html
https://www.nhooo.com/tool/scheme/
https://code.cs61a.org/
Scheme入门教程
https://klose911.github.io/html/scheme/tutorial/slt.html
https://deathking.github.io/yast-cn/
minilisp语法(参考Scheme教程)
待修订…
c | 是否必须 | mini lisp | |
---|---|---|---|
运算符 | ****************************** | ||
算术运算符 | + -*/% | + - * / remainder(余) quotient(商) | |
关系运算符 | == != > < >= <= | eq? = < > <= >= | |
逻辑运算符 | && || ! | and, or, not | |
位运算符 | & | ^ ~ << >> | N/A | |
赋值运算符 | = | let | |
运算符优先级 | 用()改变求值顺序。 | 否 | () |
数据 | ****************************** | ||
keys | void*,unsigned,char,int,long,double,struct | atoms, numbers, lists | |
取地址 | void *p=&i; | N/A | |
内存读写 | *p=i; 通过指针读写n维数组 | N/A | |
变量定义 | int i; void*p; | 否 | (define x 10) |
结构体 | struct s{int a; double c;}; | 否 | N/A |
内存管理 | malloc() free() | 否 | N/A |
控制 | ****************************** | ||
keys | if,else,goto,do,while,break,continue,return | if, cond, quote, lambda, define | |
分支控制 | if(…){goto}else{goto} | (if test then else) | |
错误处理 | return -1; 对应图灵机的停机 | N/A | |
程序退出状态 | return 0; 对应图灵机的停机 | N/A | |
中断 | eg:除0,goto到程序异常出口。对应图灵机的停机 | N/A | |
行结尾 | ; 对应图灵机的状态集成员,方便程序实现控制 | () | |
代码块 | {} 对应图灵机的状态集成员,方便程序实现控制 | (begin …) | |
路径分隔符 | / 对应图灵机的状态集成员,方便程序实现控制 | N/A | |
标识符命名规则 | [_A-Za-z0-9] 区分大小写。 | 类似c | |
递归 | 一般通过递归函数实现。 | 否 | 通过递归函数定义 |
循环控制 | do{break;continue;}while(…); | 否 | 使用递归实现循环 |
函数定义 | int max(int x, int y) {…} | 否 | (define (f x) …) |
函数调用 | int ret = max(a, b); | 否 | (f x) |
函数指针 | int (*)(int,int) p=max; | 否 | N/A |
输入输出 | ****************************** | ||
程序入口及输入输出 | int main( int argc, char *argv[] ){… return 0;} | N/A | |
文件读写 | int fopen(); fclose(); fread(…); fwrite(…); 一切皆文件。 | 否 | N/A |
基础算法 | ****************************** | ||
随机数 | srand() rand() | 否 | N/A |
排序 | qsort() | 否 | N/A |
查找 | bsearch () | 否 | N/A |
其它 | ****************************** | ||
代码管理 | .h.c #include “head.h” | 否 | N/A |
minilisp readme 翻译
https://github.com/rui314/minilisp/blob/master/README.md
MiniLisp
有一天,我想看看用1k行C语言能做什么,于是决定写一个Lisp解释器。这变成了一个有趣的周末项目,结果是一个支持
整数,符号,cons单元,
全局变量,
词法作用域的局部变量,
闭包,
if条件语句,
基本函数,比如 +, =, <, 或 list,
用户定义的函数,
一个宏系统,
和一个复制垃圾收集器。
所有这些都在1000行C语言代码中实现。我没有为了减小体积牺牲可读性。代码在我看来有大量的注释,帮助读者理解所有这些功能是如何工作的。
编译
$ make
MiniLisp 已在 Linux x86/x86-64 和 64位 Mac OS 上测试过。代码并不非常依赖于架构,所以你应该能够在其他类Unix操作系统上编译和运行。
测试
MiniLisp 附带了一个全面的测试套件。为了运行测试,给 make 传入 "test" 参数。
$ make test
语言特性
MiniLisp 是一个传统的Lisp解释器。它一次从标准输入读取一个表达式,评估它,然后打印出表达式的返回值。这里是一个有效输入的例子。
(+ 1 2)
上面的表达式打印 "3"。
字面量
MiniLisp 支持整数字面量,(),t,符号,和列表字面量。
整数字面量是正数或负数。
() 是唯一的假值。它也代表空列表。
t 是一个预定义变量,评估为它本身。它是表示真值的首选方式,而任何非()值都被认为是真。
符号是具有唯一名称的对象。它们用于表示标识符。因为 MiniLisp 没有字符串类型,符号有时也用作字符串的替代品。
列表字面量是 cons 单元。它要么是一个最后一个元素的 cdr 是 () 的常规列表,要么是一个以任何非()值结束的点列表。点列表写作 (a . b)。
列表操作符
cons 接受两个参数并返回一个新的 cons 单元,使第一个参数成为 car,第二个成为 cdr。
(cons 'a 'b) ; -> (a . b)
(cons 'a '(b)) ; -> (a b)
car 和 cdr 是 cons 单元的访问器。car 返回 car,cdr 返回 cdr。
(car '(a . b)) ; -> a
(cdr '(a . b)) ; -> b
setcar 变异一个 cons 单元。setcar 接受两个参数,假设第一个参数是一个 cons 单元。它将第二个参数的值设置为 cons 单元的 car。
(define cell (cons 'a 'b))
cell ; -> (a . b)
(setcar cell 'x)
cell ; -> (x . b)
数值操作符
+ 返回参数的和。
(+ 1) ; -> 1
(+ 1 2) ; -> 3
(+ 1 2 3) ; -> 6
- 如果只给出一个参数,则取该参数的相反数。
(- 3) ; -> -3
(- -5) ; -> 5
如果给出多个参数,- 从第一个参数中减去每个参数。
(- 5 2) ; -> 3
(- 5 2 7) ; -> -4
= 接受两个参数,如果两者是相同的整数,则返回 t。
(= 11 11) ; -> t
(= 11 6) ; -> ()
< 接受两个参数,如果第一个参数小于第二个,则返回 t。
(< 2 3) ; -> t
(< 3 3) ; -> ()
(< 4 3) ; -> ()
条件语句
(if cond then else) 是语言中唯一的条件语句。它首先评估 cond。如果结果是真值,则评估 then。否则评估 else。
循环
(while cond expr ...) 执行 expr ... 直到 cond 被评估为 ()。这是 MiniLisp 支持的唯一循环。
如果你熟悉 Scheme,你可能会想知道你是否可以在 MiniLisp 中通过尾递归写一个循环。答案是不行。尾调用在 MiniLisp 中消耗堆栈空间,所以作为递归写的循环会因内存耗尽错误而失败。
等价测试操作符
eq 接受两个参数,如果对象是相同的,则返回 t。eq 实际上做的是指针比较,所以两个对象偶然有相同的内容但实际上不同被 eq 认为不是相同的。
输出操作符
println 将给定对象打印到标准输出。
(println 3) ; 打印 "3"
(println '(hello world)) ; 打印 "(hello world)"
定义
MiniLisp 支持变量和函数。它们可以使用 define 来定义。
(define a (+ 1 2))
(+ a a) ; -> 6
有两种方式定义函数。一种方式是使用特殊形式 lambda。 (lambda (args ...) expr ...) 返回一个函数对象,你可以使用 define 将其赋值给一个变量。
(define double (lambda (x) (+ x x)))
(double 6) ; -> 12
((lambda (x) (+ x x)) 6) ; 做同样的事情而不赋值
另一种方式是 defun。 (defun fn (args ...) expr ...) 是 (define fn (lambda (args ...) expr ...)) 的简写。
;; 使用 defun 定义 "double"
(defun double (x) (+ x x))
你可以写一个接受可变数量参数的函数。如果参数列表是一个点列表,剩余的参数被绑定到最后一个参数作为一个列表。
(defun fn (expr . rest) rest)
(fn 1) ; -> ()
(fn 1 2 3) ; -> (2 3)
变量是词法作用域的,并且有无限的范围。即使创建变量的函数返回后,对“外部”变量的引用仍然有效。
;; 一个计数函数。我们使用 lambda 引入局部变量,因为我们
;; 没有 "let" 之类的。
(define counter
((lambda (count)
(lambda ()
(setq count (+ count 1))
count))
0))
(counter) ; -> 1
(counter) ; -> 2
;; 这将不会返回 12345 而是 3。counter 函数中的变量 "count"
;; 是基于其词法上下文而不是动态上下文解析的。
((lambda (count) (counter)) 12345) ; -> 3
setq 给一个现有的变量设置一个新值。如果变量未定义,则是一个错误。
(define val (+ 3 5))
(setq val (+ val 1)) ; 增加 "val"
宏
宏看起来类似于函数,但它们不同,宏以表达式作为输入并返回一个新表达式作为输出。 (defmacro macro-name (args ...) body ...) 定义一个宏。这里是一个例子。
(defmacro unless (condition expr)
(list 'if condition () expr))
上面的 defmacro 定义了一个新的宏 unless。unless 是一个新的条件语句,除非 condition 是真值,否则评估 expr。你不能用函数做同样的事情,因为所有的参数会在控制权传递给函数之前被评估。
(define x 0)
(unless (= x 0) '(x is not 0)) ; -> ()
(unless (= x 1) '(x is not 1)) ; -> (x is not 1)
macroexpand 是一个方便的特殊形式,
用来查看宏的展开形式。
(macroexpand (unless (= x 1) '(x is not 1)))
;; -> (if (= x 1) () (quote (x is not 1)))
gensym 创建一个新的符号,它永远不会与除了它自己以外的任何其他符号 eq。用于编写引入新标识符的宏时很有用。
(gensym) ; -> 一个新的符号
注释
如同传统的 Lisp 语法,;(分号)开始一个单行注释。注释继续到行尾。
无 GC 分支
有一个 MiniLisp 分支,其代码中已去除了垃圾收集的代码。接受的语言是相同的,但代码比 master 分支的更简单。读者可能想先阅读 nogc 分支,然后继续到 master 分支,逐步理解代码。
nogc 分支可在 nogc 处获得。原版可在 master 处获得。
test脚本说明
#!/bin/bash
//输出一个红色的错误信息 [ERROR] 和自定义的错误消息,然后退出脚本。这个函数在测试失败时被调用。
function fail() {
echo -n -e '\e[1;31m[ERROR]\e[0m '
echo "$1"
exit 1
}
//执行MiniLisp编译器并捕获任何标准错误输出。
//如果有错误输出,打印 FAILED 并调用 fail 函数显示错误信息。
//接着,它会执行编译器并只捕获标准输出的最后一行(使用 tail -1)。
//然后检查这个输出是否符合预期结果。
//如果不符合,打印 FAILED 并调用 fail 函数显示预期结果和实际结果。
function do_run() {
error=$(echo "$3" | ./minilisp 2>&1 > /dev/null)
if [ -n "$error" ]; then
echo FAILED
fail "$error"
fi
result=$(echo "$3" | ./minilisp 2> /dev/null | tail -1)
if [ "$result" != "$2" ]; then
echo FAILED
fail "$2 expected, but got $result"
fi
}
# 以run + 3 '(+ 1 2)' 测试为例进行说明
# error=$(echo "$3" | ./minilisp 2>&1 > /dev/null) 实际是以下测试
# echo '(+ 1 2)' | ./minilisp 2>&1 > /dev/null
# echo '(+ 1 2)'的结果作为./minilisp的参数
# 2>&1 这是一个重定向操作,它将标准错误(file descriptor 2)重定向到标准输出(file descriptor 1)。
# 这意味着 ./minilisp 的错误输出和正常输出都会合并。
# 标准输出(stdout)被重定向到 /dev/null,意味着标准输出会被丢弃。
# result=$(echo "$3" | ./minilisp 2> /dev/null | tail -1) 实际是以下测试
# result=$(echo '(+ 1 2)' | ./minilisp 2> /dev/null | tail -1)
# echo '(+ 1 2)' | ./minilisp 2> /dev/null | tail -1 的输出就是3
# result等于stdout输出的值3
//打印测试名称和正在进行的提示,然后调用 do_run 函数执行实际的测试。
//它会两次运行每个测试用例:
//一次是在正常条件下,
//另一次是在设置了环境变量 MINILISP_ALWAYS_GC=1 的条件下,
//这可能会触发垃圾收集器以测试其在不同设置下的行为。
function run() {
echo -n "Testing $1 ... "
# Run the tests twice to test the garbage collector with different settings.
MINILISP_ALWAYS_GC= do_run "$@"
MINILISP_ALWAYS_GC=1 do_run "$@"
echo ok
}
# 以run + 3 '(+ 1 2)' 测试为例进行说明
# $1 == + 测试项名称
# $2 == 3 测试的期望结果
# $3 == '(+ 1 2)' 测试的表达式
//测试用例
//每个 run 调用都定义了一个测试用例,包括测试的名称、期望的输出和MiniLisp表达式。
//测试范围包括基本数据类型、列表操作、条件表达式、
// 全局变量和局部变量的使用、函数和宏的定义与调用、
// 递归、闭包、循环控制结构等。
# Basic data types
run integer 1 1 //测试MiniLisp编译器能否正确处理整数1,并返回1。
run integer -1 -1
run symbol a "'a" //测试编译器对符号a的处理,期望输出为a。
run quote a "(quote a)"
run quote 63 "'63"
run quote '(+ 1 2)' "'(+ 1 2)"
run + 3 '(+ 1 2)' //测试加法运算符,检查表达式(+ 1 2)是否正确计算为3。
run + -2 '(+ 1 -3)'
run 'unary -' -3 '(- 3)'
run '-' -2 '(- 3 5)'
run '-' -9 '(- 3 5 7)'
run '<' t '(< 2 3)'
run '<' '()' '(< 3 3)'
run '<' '()' '(< 4 3)'
run 'literal list' '(a b c)' "'(a b c)"
run 'literal list' '(a b . c)' "'(a b . c)"
# List manipulation
run cons "(a . b)" "(cons 'a 'b)"
run cons "(a b c)" "(cons 'a (cons 'b (cons 'c ())))"
run car a "(car '(a b c))"
run cdr "(b c)" "(cdr '(a b c))"
run setcar "(x . b)" "(define obj (cons 'a 'b)) (setcar obj 'x) obj"
# Comments
run comment 5 "
; 2
5 ; 3"
# Global variables
run define 7 '(define x 7) x'
run define 10 '(define x 7) (+ x 3)'
run define 7 '(define + 7) +'
run setq 11 '(define x 7) (setq x 11) x'
run setq 17 '(setq + 17) +'
# Conditionals
run if a "(if 1 'a)"
run if '()' "(if () 'a)"
run if a "(if 1 'a 'b)" //测试if条件表达式,检查在条件为真(这里用1表示真)时是否选择了正确的分支。
run if a "(if 0 'a 'b)"
run if a "(if 'x 'a 'b)"
run if b "(if () 'a 'b)"
run if c "(if () 'a 'b 'c)"
# Numeric comparisons
run = t '(= 3 3)'
run = '()' '(= 3 2)'
# eq
run eq t "(eq 'foo 'foo)"
run eq t "(eq + +)"
run eq '()' "(eq 'foo 'bar)"
run eq '()' "(eq + 'bar)"
# gensym
run gensym G__0 '(gensym)'
run gensym '()' "(eq (gensym) 'G__0)"
run gensym '()' '(eq (gensym) (gensym))'
run gensym t '((lambda (x) (eq x x)) (gensym))'
# Functions
run lambda '<function>' '(lambda (x) x)'
run lambda t '((lambda () t))'
run lambda 9 '((lambda (x) (+ x x x)) 3)'
run defun 12 '(defun double (x) (+ x x)) (double 6)'
run args 15 '(defun f (x y z) (+ x y z)) (f 3 5 7)'
run restargs '(3 5 7)' '(defun f (x . y) (cons x y)) (f 3 5 7)'
run restargs '(3)' '(defun f (x . y) (cons x y)) (f 3)'
# Lexical closures
run closure 3 '(defun call (f) ((lambda (var) (f)) 5))
((lambda (var) (call (lambda () var))) 3)'
run counter 3 '
(define counter
((lambda (val)
(lambda () (setq val (+ val 1)) val))
0))
(counter)
(counter)
(counter)'
# While loop
run while 45 "
(define i 0)
(define sum 0)
(while (< i 10)
(setq sum (+ sum i))
(setq i (+ i 1)))
sum"
# Macros
run macro 42 "
(defun list (x . y) (cons x y))
(defmacro if-zero (x then) (list 'if (list '= x 0) then))
(if-zero 0 42)"
run macro 7 '(defmacro seven () 7) ((lambda () (seven)))'
run macroexpand '(if (= x 0) (print x))' "
(defun list (x . y) (cons x y))
(defmacro if-zero (x then) (list 'if (list '= x 0) then))
(macroexpand (if-zero x (print x)))"
# Sum from 0 to 10
run recursion 55 '(defun f (x) (if (= x 0) 0 (+ (f (+ x -1)) x))) (f 10)'
Makefile
CFLAGS=-std=gnu99 -g -O2 -Wall
.PHONY: clean test // 这一行声明了两个伪目标clean和test。
//伪目标是指实际上不对应于文件的目标;
//即使存在与这些目标同名的文件,执行make时也会无条件执行其命令。
minilisp: minilisp.c
clean:
rm -f minilisp *~ //所有以波浪符号(~)结尾的文件,这些通常是文本编辑器自动生成的备份文件。
test: minilisp
@./test.sh //@符号的作用是不在make的输出中显示该命令,只显示其输出和错误。
minilisp.c代码注释及学习
先机器翻译,后面再根据debug加注释。
```c
// 这个软件属于公共领域。
#include <assert.h>
#include <ctype.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
static __attribute((noreturn)) void error(char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
fprintf(stderr, "\n");
va_end(ap);
exit(1);
}
//======================================================================
// Lisp 对象
//======================================================================
// Lisp 对象类型
enum {
// 用户可见的常规对象
TINT = 1,
TCELL,
TSYMBOL,
TPRIMITIVE,
TFUNCTION,
TMACRO,
TENV,
// GC 时标记对象已被移动到其他位置的标记。新位置可以通过转发指针找到。只有执行垃圾收集的函数会设置和处理这种类型的对象。其他函数永远不会看到这种类型的对象。
TMOVED,
// 常量对象。它们是静态分配的,永远不会被 GC 管理。
TTRUE,
TNIL,
TDOT,
TCPAREN,
};
// 原始函数的 Typedef
struct Obj;
typedef struct Obj *Primitive(void *root, struct Obj **env, struct Obj **args);
// 对象类型
typedef struct Obj {
// 对象的第一个字表示对象的类型。任何处理对象的代码首先需要检查它的类型,然后访问以下联合成员。
int type;
// 对象的总大小,包括 "type" 字段、此字段、内容和对象末尾的填充。
int size;
// 对象值。
union {
// Int
int value;
// Cell
struct {
struct Obj *car;
struct Obj *cdr;
};
// Symbol
char name[1];
// Primitive
Primitive *fn;
// Function 或 Macro
struct {
struct Obj *params;
struct Obj *body;
struct Obj *env;
};
// 环境帧。这是一个关联列表的链表
// 包含从符号到它们值的映射。
struct {
struct Obj *vars;
struct Obj *up;
};
// 转发指针
void *moved;
};
} Obj;
// 常量
static Obj *True = &(Obj){ TTRUE };
static Obj *Nil = &(Obj){ TNIL };
static Obj *Dot = &(Obj){ TDOT };
static Obj *Cparen = &(Obj){ TCPAREN };
// 包含所有符号的列表。这种数据结构传统上称为 "obarray",但我
// 避免使用它作为变量名,因为这不是一个数组而是一个列表。
static Obj *Symbols;
//======================================================================
// 内存管理
//======================================================================
// 堆的大小,以字节为单位
#define MEMORY_SIZE 65536
// 指向当前堆开始的指针
static void *memory;
// 指向旧堆开始的指针
static void *from_space;
// 从堆分配的字节数
static size_t mem_nused = 0;
// 用于调试 GC 的标志
static bool gc_running = false;
static bool debug_gc = false;
static bool always_gc = false;
static void gc(void *root);
// 目前我们使用的是 Cheney 的复制垃圾收集(GC)算法,该算法将可用内存分为两半,并且每次调用 GC 时都会将所有对象从一半移动到另一半。
// 这意味着对象的地址会不断变化。如果你取了一个对象的地址并将其保留在一个 C 变量中,那么在 GC 运行后解引用它可能会导致 SEGV,
// 因为地址变得无效了。
//
// 为了解决这个问题,所有从 C 到 Lisp 对象的访问都将通过两级指针解引用来进行。C 本地变量指向 C 栈上的一个指针,该指针又指向 Lisp 对象。
// GC 知道栈中的指针,并在 GC 发生时更新它们的内容,以反映对象的新地址。
//
// 下面是一个宏,用于在 C 栈中为指针预留空间。这个区域的内容被认为是 GC 的根。
//
// 注意不要绕过两级指针间接访问。如果你创建了一个直接指向对象的指针,它会导致一个微妙的错误。这样的代码在大多数情况下都能工作,
// 但如果在代码执行期间发生 GC,则会失败并导致 SEGV。任何分配内存的代码都可能调用 GC。
#define ROOT_END ((void *)-1)
#define ADD_ROOT(size) \
void *root_ADD_ROOT_[size + 2]; \
root_ADD_ROOT_[0] = root; \
for (int i = 1; i <= size; i++) \
root_ADD_ROOT_[i] = NULL; \
root_ADD_ROOT_[size + 1] = ROOT_END; \
root = root_ADD_ROOT_
#define DEFINE1(var1) \
ADD_ROOT(1); \
Obj **var1 = (Obj **)(root_ADD_ROOT_ + 1)
#define DEFINE2(var1, var2) \
ADD_ROOT(2); \
Obj **var1 = (Obj **)(root_ADD_ROOT_ + 1); \
Obj **var2 = (Obj **)(root_ADD_ROOT_ + 2)
#define DEFINE3(var1, var2, var3) \
ADD_ROOT(3); \
Obj **var1 = (Obj **)(root_ADD_ROOT_ + 1); \
Obj **var2 = (Obj **)(root_ADD_ROOT_ + 2); \
Obj **var3 = (Obj **)(root_ADD_ROOT_ + 3)
#define DEFINE4(var1, var2, var3, var4) \
ADD_ROOT(4); \
Obj **var1 = (Obj **)(root_ADD_ROOT_ + 1); \
Obj **var2 = (Obj **)(root_ADD_ROOT_ + 2); \
Obj **var3 = (Obj **)(root_ADD_ROOT_ + 3); \
Obj **var4 = (Obj **)(root_ADD_ROOT_ + 4)
// 将给定的值向上舍入到 2 的幂的倍数。首先加上 size - 1,
// 然后将最低有效位归零,使结果成为 size 的倍数。我知道这些位操作看起来可能有点棘手,但它们是高效的,因此经常被使用。
static inline size_t roundup(size_t var, size_t size) {
return (var + size - 1) & ~(size - 1);
}
// 分配内存块。如果我们没有足够的内存,这可能会启动 GC。
static Obj *alloc(void *root, int type, size_t size) {
// 对象必须足够大,能够包含一个用于转发指针的指针。如果它小于那个大小,则使其变大。
size = roundup(size, sizeof(void *));
// 添加类型标签和大小字段的大小。
size += offsetof(Obj, value);
// 将对象大小向上舍入到最近的对齐边界,以便下
一个对象将在适当的对齐边界上分配。目前我们将对象与指针相同的边界对齐。
size = roundup(size, sizeof(void *));
// 如果调试标志打开,分配一个新的内存空间来强制所有现有对象移动到新地址,以使旧地址失效。通过这样做,GC 行为变得更加可预测和可重复。
// 如果 C 变量直接引用了一个 Lisp 对象,这个 GC 调用会使指针失效。解引用它会立即导致 SEGV。
if (always_gc && !gc_running)
gc(root);
// 否则,只有在可用内存不足时才运行 GC。
if (!always_gc && MEMORY_SIZE < mem_nused + size)
gc(root);
// 如果我们无法满足内存请求,则终止程序。如果请求的大小太大或 from-space 中有太多活动对象,这可能会发生。
if (MEMORY_SIZE < mem_nused + size)
error("Memory exhausted");
// 分配对象。
Obj *obj = memory + mem_nused;
obj->type = type;
obj->size = size;
mem_nused += size;
return obj;
}
//======================================================================
// 垃圾收集器
//======================================================================
// Cheney 算法使用两个指针来跟踪 GC 状态。起初两个指针都指向 to-space 的开始。随着 GC 的进行,它们被移向 to-space 的末端。
// 在 "scan1" 之前的对象是已经完全复制过的对象。"scan1" 和 "scan2" 之间的对象已经被复制,但可能包含指向 from-space 的指针。
// "scan2" 指向空闲空间的开始。
static Obj *scan1;
static Obj *scan2;
// 将一个对象从 from-space 移动到 to-space。返回对象的新地址。如果对象已经被移动,则什么也不做,只是返回新地址。
static inline Obj *forward(Obj *obj) {
// 如果对象的地址不在 from-space 中,则该对象不由 GC 管理,或者它已经被移动到 to-space 中。
ptrdiff_t offset = (uint8_t *)obj - (uint8_t *)from_space;
if (offset < 0 || MEMORY_SIZE <= offset)
return obj;
// 指针指向 from-space,但那里的对象是一个墓碑。跟随转发指针找到对象的新位置。
if (obj->type == TMOVED)
return obj->moved;
// 否则,对象尚未被移动。移动它。
Obj *newloc = scan2;
memcpy(newloc, obj, obj->size);
scan2 = (Obj *)((uint8_t *)scan2 + obj->size);
// 在对象原来占据的位置放置一个墓碑,以便后续的 forward() 调用可以找到对象的新位置。
obj->type = TMOVED;
obj->moved = newloc;
return newloc;
}
static void *alloc_semispace() {
return mmap(NULL, MEMORY_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
}
// 复制根对象。
static void forward_root_objects(void *root) {
Symbols = forward(Symbols);
for (void **frame = root; frame; frame = *(void ***)frame)
for (int i = 1; frame[i] != ROOT_END; i++)
if (frame[i])
frame[i] = forward(frame[i]);
}
// 实现 Cheney 的复制垃圾收集算法。
// http://en.wikipedia.org/wiki/Cheney%27s_algorithm
static void gc(void *root) {
assert(!gc_running);
gc_running = true;
// 分配一个新的半空间。
from_space = memory;
memory = alloc_semispace();
// 初始化两个用于 GC 的指针。最初它们指向 to-space 的开始。
scan1 = scan2 = memory;
// 首先复制 GC 根对象。这会移动指针 scan2。
forward_root_objects(root);
// 复制位于 scan1 和 scan2 之间的 GC 根对象引用的对象。一旦完成,所有活动对象(即可从根到达的对象)都将被复制到 to-space 中。
while (scan1 < scan2) {
switch (scan1->type) {
case TINT:
case TSYMBOL:
case TPRIMITIVE:
// 上述类型中的任何一个都不包含指向 GC 管理对象的指针。
break;
case TCELL:
scan1->car = forward(scan1->car);
scan1->cdr = forward(scan1->cdr);
break;
case TFUNCTION:
case TMACRO:
scan1->params = forward(scan1->params);
scan1->body = forward(scan1->body);
scan1->env = forward(scan1->env);
break;
case TENV:
scan1->vars = forward(scan1->vars);
scan1->up = forward(scan1->up);
break;
default:
error("Bug: copy: unknown type %d", scan1->type);
}
scan1 = (Obj *)((uint8_t *)scan1 + scan1->size);
}
// 完成 GC。
munmap(from_space, MEMORY_SIZE);
size_t old_nused = mem_nused;
mem_nused = (size_t)((uint8_t *)scan1 - (uint8_t *)memory);
if (debug_gc)
fprintf(stderr, "GC: %zu bytes out of %zu bytes copied.\n", mem_nused, old_nused);
gc_running = false;
}
//======================================================================
// 构造函数
//======================================================================
static Obj *make_int(void *root, int value) {
Obj *r = alloc(root, TINT, sizeof(int));
r->value = value;
return r;
}
static Obj *cons(void *root, Obj **car, Obj **cdr) {
Obj *cell = alloc(root, TCELL, sizeof(Obj *) * 2);
cell->car = *car;
cell->cdr = *cdr;
return cell;
}
static Obj *make_symbol(void *root, char *name) {
Obj *sym = alloc(root, TSYMBOL, strlen(name) + 1);
strcpy(sym->name, name);
return sym;
}
static Obj *make_primitive(void *root, Primitive *fn) {
Obj *r = alloc(root, TPRIMITIVE, sizeof(Primitive *));
r->fn = fn;
return r;
}
static Obj *make_function(void *root, Obj **env, int type, Obj **params, Obj **body) {
assert(type == TFUNCTION || type == TMACRO);
Obj *r = alloc(root, type, sizeof(Obj *) * 3);
r->params = *params;
r->body = *body;
r->env = *env;
return r;
}
struct Obj *make_env(void *root, Obj **vars, Obj **up) {
Obj *r = alloc(root, TENV, sizeof(Obj *) * 2);
r->vars = *vars;
r->up = *up;
return r;
}
// 返回 ((x . y) . a)
static Obj *acons(void *root, Obj **x, Obj **y, Obj **a) {
DEFINE1(cell);
*cell = cons(root, x, y);
return cons(root, cell, a);
}
//======================================================================
// 解析器
//
// 这是一个手写的递归下降解析器。
//======================================================================
#define SYMBOL_MAX_LEN 200
const char symbol_chars[] = "~!@#$%^&*-_=+:/?<>";
static Obj *read_expr(void *root);
static int peek(void) {
int c = getchar();
ungetc(c, stdin);
return c;
}
// 破坏性地反转给定列表。
static Obj *reverse(Obj *p) {
Obj *ret = Nil;
while (p != Nil) {
Obj *head = p;
p = p->cdr;
head->cdr = ret;
ret = head;
}
return ret;
}
// 跳过输入,直到找到换行符。换行符是 \r, \r\n 或 \n 中的一个。
static void skip_line(void) {
for (;;) {
int c = getchar();
if (c == EOF || c == '\n')
return;
if (c == '\r') {
if (peek() == '\n')
getchar();
return;
}
}
}
// 读取一个列表。注意,'(' 已经被读取。
static Obj *read_list(void *root) {
DEFINE3(obj, head, last);
*head = Nil;
for (;;) {
*obj = read_expr(root);
if (!*obj)
error("Unclosed parenthesis");
if (*obj == Cparen)
return reverse(*head);
if (*obj == Dot) {
*last = read_expr(root);
if (read_expr(root) != Cparen)
error("Closed parenthesis expected after dot");
Obj *ret = reverse(*head);
(*head)->cdr = *last;
return ret;
}
*head = cons(root, obj, head);
}
}
// 可能创建一个新符号。如果有同名的符号存在,它不会创建一个新符号,而是返回现有的符号。
static Obj *intern(void *root, char *name) {
for (Obj *p = Symbols; p != Nil; p = p->cdr)
if (strcmp(name, p->car->name) == 0)
return p->car;
DEFINE1(sym);
*sym = make_symbol(root, name);
Symbols = cons(root, sym, &Symbols);
return *sym;
}
// 读取宏 ' (单引号)。它读取一个表达式并返回 (quote <expr>)。
static Obj *read_quote(void *root) {
DEFINE2(sym, tmp);
*sym = intern(root, "quote");
*tmp = read_expr(root);
*tmp = cons(root, tmp, &Nil);
*tmp = cons(root, sym, tmp);
return *tmp;
}
static int read_number(int val) {
while (isdigit(peek()))
val = val * 10 + (getchar() - '0');
return val;
}
static Obj *read_symbol(void *root, char c) {
char buf[SYMBOL_MAX_LEN + 1];
buf[0] = c;
int len = 1;
while (isalnum(peek()) || strchr(symbol_chars, peek())) {
if (SYMBOL_MAX_LEN <= len)
error("Symbol name too long");
buf[len++] = getchar();
}
buf[len] = '\0';
return intern(root, buf);
}
static Obj *read_expr(void *root) {
for (;;) {
int c = getchar();
if (c == ' ' || c == '\n' || c == '\r' || c == '\t')
continue;
if (c == EOF)
return NULL;
if (c == ';') {
skip_line();
continue;
}
if (c == '(')
return read_list(root);
if (c == ')')
return Cparen;
if (c == '.')
return Dot;
if (c == '\'')
return read_quote(root);
if (isdigit(c))
return make_int(root, read_number(c - '0'));
if (c == '-' && isdigit(peek()))
return make_int(root, -read_number(0));
if (isalpha(c) || strchr(symbol_chars, c))
return read_symbol(root, c);
error("Don't know how to handle %c", c);
}
}
// 打印给定的对象。
static void print(Obj *obj) {
switch (obj->type) {
case TCELL:
printf("(");
for (;;) {
print(obj->car);
if (obj->cdr == Nil)
break;
if (obj->cdr->type != TCELL) {
printf(" . ");
print(obj->cdr);
break;
}
printf(" ");
obj = obj->cdr;
}
printf(")");
return;
#define CASE(type, ...) \
case type: \
printf(__VA_ARGS__); \
return
CASE(TINT, "%d", obj->value);
CASE(TSYMBOL, "%s", obj->name);
CASE(TPRIMITIVE, "<primitive>");
CASE(TFUNCTION, "<function>");
CASE(TMACRO, "<macro>");
CASE(TMOVED, "<moved>");
CASE(TTRUE, "t");
CASE(TNIL, "()");
#undef CASE
default:
error("Bug: print: Unknown tag type: %d", obj->type);
}
}
// 返回给定列表的长度。如果它不是一个正确的列表,则返回 -1。
static int length(Obj *list) {
int len = 0;
for (; list->type == TCELL; list = list->cdr)
len++;
return list == Nil ? len : -1;
}
//======================================================================
// 评估器
//======================================================================
static Obj *eval(void *root, Obj **env, Obj **obj);
static void add_variable(void *root, Obj **env, Obj **sym, Obj **val) {
DEFINE2(vars, tmp);
*vars = (*env)->vars;
*tmp = acons(root, sym, val, vars);
(*env)->vars = *tmp;
}
// 返回新创建的环境帧。
static Obj *push_env(void *root, Obj **env, Obj **vars, Obj **vals) {
DEFINE3(map, sym, val);
*map = Nil;
for (; (*vars)->type == TCELL; *vars = (*vars)->cdr, *vals = (*vals)->cdr) {
if ((*vals)->type != TCELL)
error("无法应用函数:参数数量不匹配");
*sym = (*vars)->car;
*val = (*vals)->car;
*map = acons(root, sym, val, map);
}
if (*vars != Nil)
*map = acons(root, vars, vals, map);
return make_env(root, map, env);
}
// 从头开始评估列表元素,并返回最后的返回值。
static Obj *progn(void *root, Obj **env, Obj **list) {
DEFINE2(lp, r);
for (*lp = *list; *lp != Nil; *lp = (*lp)->cdr) {
*r = (*lp)->car;
*r = eval(root, env, r);
}
return *r;
}
// 评估所有列表元素,并将它们的返回值作为一个新列表返回。
static Obj *eval_list(void *root, Obj **env, Obj **list) {
DEFINE4(head, lp, expr, result);
*head = Nil;
for (lp = list; *lp != Nil; *lp = (*lp)->cdr) {
*expr = (*lp)->car;
*result = eval(root, env, expr);
*head = cons(root, result, head);
}
return reverse(*head);
}
static bool is_list(Obj *obj) {
return obj == Nil || obj->type == TCELL;
}
static Obj *apply_func(void *root, Obj **env, Obj **fn, Obj **args) {
DEFINE3(params, newenv, body);
*params = (*fn)->params;
*newenv = (*fn)->env;
*newenv = push_env(root, newenv, params, args);
*body = (*fn)->body;
return progn(root, newenv, body);
}
// 使用参数应用fn。
static Obj *apply(void *root, Obj **env, Obj **fn, Obj **args) {
if (!is_list(*args))
error("参数必须是列表");
if ((*fn)->type == TPRIMITIVE)
return (*fn)->fn(root, env, args);
if ((*fn)->type == TFUNCTION) {
DEFINE1(eargs);
*eargs = eval_list(root, env, args);
return apply_func(root, env, fn, eargs);
}
error("不支持的类型");
}
// 通过符号搜索变量。如果找不到则返回null。
static Obj *find(Obj **env, Obj *sym) {
for (Obj *p = *env; p != Nil; p = p->up) {
for (Obj *cell = p->vars; cell != Nil; cell = cell->cdr) {
Obj *bind = cell->car;
if (sym == bind->car)
return bind;
}
}
return NULL;
}
// 展开给定的宏应用形式。
static Obj *macroexpand(void *root, Obj **env, Obj **obj) {
if ((*obj)->type != TCELL || (*obj)->car->type != TSYMBOL)
return *obj;
DEFINE3(bind, macro, args);
*bind = find(env, (*obj)->car);
if (!*bind || (*bind)->cdr->type != TMACRO)
return *obj;
*macro = (*bind)->cdr;
*args = (*obj)->cdr;
return apply_func(root, env, macro, args);
}
// 评估S表达式。
static Obj *eval(void *root, Obj **env, Obj **obj) {
switch ((*obj)->type) {
case TINT:
case TPRIMITIVE:
case TFUNCTION:
case TTRUE:
case TNIL:
// 自评估对象
return *obj;
case
TSYMBOL: {
// 变量
Obj *bind = find(env, *obj);
if (!bind)
error("未定义的符号: %s", (*obj)->name);
return bind->cdr;
}
case TCELL: {
// 函数应用形式
DEFINE3(fn, expanded, args);
*expanded = macroexpand(root, env, obj);
if (*expanded != *obj)
return eval(root, env, expanded);
*fn = (*obj)->car;
*fn = eval(root, env, fn);
*args = (*obj)->cdr;
if ((*fn)->type != TPRIMITIVE && (*fn)->type != TFUNCTION)
error("列表的头部必须是函数");
return apply(root, env, fn, args);
}
default:
error("Bug: eval: 未知的标签类型: %d", (*obj)->type);
}
}
//======================================================================
// 基本函数和特殊形式
//======================================================================
// 'expr
static Obj *prim_quote(void *root, Obj **env, Obj **list) {
if (length(*list) != 1)
error("Malformed quote");//引用格式错误
return (*list)->car;
}
// (cons expr expr)
static Obj *prim_cons(void *root, Obj **env, Obj **list) {
if (length(*list) != 2)
error("构造函数cons格式错误");
Obj *cell = eval_list(root, env, list);
cell->cdr = cell->cdr->car;
return cell;
}
// (car <cell>)
static Obj *prim_car(void *root, Obj **env, Obj **list) {
Obj *args = eval_list(root, env, list);
if (args->car->type != TCELL || args->cdr != Nil)
error("car函数格式错误");
return args->car->car;
}
// (cdr <cell>)
static Obj *prim_cdr(void *root, Obj **env, Obj **list) {
Obj *args = eval_list(root, env, list);
if (args->car->type != TCELL || args->cdr != Nil)
error("cdr函数格式错误");
return args->car->cdr;
}
// (setq <symbol> expr)
static Obj *prim_setq(void *root, Obj **env, Obj **list) {
if (length(*list) != 2 || (*list)->car->type != TSYMBOL)
error("setq函数格式错误");
DEFINE2(bind, value);
*bind = find(env, (*list)->car);
if (!*bind)
error("未绑定变量 %s", (*list)->car->name);
*value = (*list)->cdr->car;
*value = eval(root, env, value);
(*bind)->cdr = *value;
return *value;
}
// (setcar <cell> expr)
static Obj *prim_setcar(void *root, Obj **env, Obj **list) {
DEFINE1(args);
*args = eval_list(root, env, list);
if (length(*args) != 2 || (*args)->car->type != TCELL)
error("setcar函数格式错误");
(*args)->car->car = (*args)->cdr->car;
return (*args)->car;
}
// (while cond expr ...)
static Obj *prim_while(void *root, Obj **env, Obj **list) {
if (length(*list) < 2)
error("while循环格式错误");
DEFINE2(cond, exprs);
*cond = (*list)->car;
while (eval(root, env, cond) != Nil) {
*exprs = (*list)->cdr;
eval_list(root, env, exprs);
}
return Nil;
}
// (gensym)
static Obj *prim_gensym(void *root, Obj **env, Obj **list) {
static int count = 0;
char buf[10];
snprintf(buf, sizeof(buf), "G__%d", count++);
return make_symbol(root, buf);
}
// (+ <integer> ...)
static Obj *prim_plus(void *root, Obj **env, Obj **list) {
int sum = 0;
for (Obj *args = eval_list(root, env, list); args != Nil; args = args->cdr) {
if (args->car->type != TINT)
error("+函数只接受数字类型参数");
sum += args->car->value;
}
return make_int(root, sum);
}
// (- <integer> ...)
static Obj *prim_minus(void *root, Obj **env, Obj **list) {
Obj *args = eval_list(root, env, list);
for (Obj *p = args; p != Nil; p = p->cdr)
if (p->car->type != TINT)
error("-函数只接受数字类型参数");
if (args->cdr == Nil)
return make_int(root, -args->car->value);
int r = args->car->value;
for (Obj *p = args->cdr; p != Nil; p = p->cdr)
r -= p->car->value;
return make_int(root, r);
}
// (< <integer> <integer>)
static Obj *prim_lt(void *root, Obj **env, Obj **list) {
Obj *args = eval_list(root, env, list);
if (length(args) != 2)
error("<函数格式错误");
Obj *x = args->car;
Obj *y = args->cdr->car;
if (x->type != TINT || y->type != TINT)
error("<函数只接受数字类型参数");
return x->value < y->value ? True : Nil;
}
// 处理函数定义
static Obj *handle_function(void *root, Obj **env, Obj **list, int type)
{
if ((*list)->type != TCELL || !is_list((*list)->car) || (*list)->cdr->type != TCELL)
error("lambda函数格式错误");
Obj *p = (*list)->car;
for (; p->type == TCELL; p = p->cdr)
if (p->car->type != TSYMBOL)
error("参数必须是符号");
if (p != Nil && p->type != TSYMBOL)
error("参数必须是符号");
DEFINE2(params, body);
*params = (*list)->car;
*body = (*list)->cdr;
return make_function(root, env, type, params, body);
}
// (lambda (<symbol> ...) expr ...)
static Obj *prim_lambda(void *root, Obj **env, Obj **list) {
return handle_function(root, env, list, TFUNCTION);
}
// 处理函数和宏的定义
static Obj *handle_defun(void *root, Obj **env, Obj **list, int type) {
if ((*list)->car->type != TSYMBOL || (*list)->cdr->type != TCELL)
error("defun函数格式错误");
DEFINE3(fn, sym, rest);
*sym = (*list)->car;
*rest = (*list)->cdr;
*fn = handle_function(root, env, rest, type);
add_variable(root, env, sym, fn);
return *fn;
}
// (defun <symbol> (<symbol> ...) expr ...)
static Obj *prim_defun(void *root, Obj **env, Obj **list) {
return handle_defun(root, env, list, TFUNCTION);
}
// (define <symbol> expr)
static Obj *prim_define(void *root, Obj **env, Obj **list) {
if (length(*list) != 2 || (*list)->car->type != TSYMBOL)
error("define函数格式错误");
DEFINE2(sym, value);
*sym = (*list)->car;
*value = (*list)->cdr->car;
*value = eval(root, env, value);
add_variable(root, env, sym, value);
return *value;
}
// (defmacro <symbol> (<symbol> ...) expr ...)
static Obj *prim_defmacro(void *root, Obj **env, Obj **list) {
return handle_defun(root, env, list, TMACRO);
}
// (macroexpand expr)
static Obj *prim_macroexpand(void *root, Obj **env, Obj **list) {
if (length(*list) != 1)
error("macroexpand函数格式错误");
DEFINE1(body);
*body = (*list)->car;
return macroexpand(root, env, body);
}
// (println expr)
static Obj *prim_println(void *root, Obj **env, Obj **list) {
DEFINE1(tmp);
*tmp = (*list)->car;
print(eval(root, env, tmp));
printf("\n");
return Nil;
}
// (if expr expr expr ...)
static Obj *prim_if(void *root, Obj **env, Obj **list) {
if (length(*list) < 2)
error("if条件表达式格式错误");
DEFINE3(cond, then, els);
*cond = (*list)->car;
*cond = eval(root, env, cond);
if (*cond != Nil) {
*then = (*list)->cdr->car;
return eval(root, env, then);
}
*els = (*list)->cdr->cdr;
return *els == Nil ? Nil : progn(root, env, els);
}
// (= <integer> <integer>)
static Obj *prim_num_eq(void *root, Obj **env, Obj **list) {
if (length(*list) != 2)
error("=函数格式错误");
Obj *values = eval_list(root, env, list);
Obj *x = values->car;
Obj *y = values->cdr->car;
if (x->type != TINT || y->type != TINT)
error("=函数只接受数字类型参数");
return x->value == y->value ? True : Nil;
}
// (eq expr expr)
static Obj *prim_eq(void *root, Obj **env, Obj **list) {
if (length(*list) != 2)
error("eq函数格式错误");
Obj *values = eval_list(root, env, list);
return values->car == values->cdr->car ? True : Nil;
}
static void add_primitive(void *root, Obj **env, char *name, Primitive *fn) {
DEFINE2(sym, prim);
*sym = intern(root, name);
*prim = make_primitive(root, fn);
add_variable(root, env, sym, prim);
}
static void define_constants(void *root, Obj **env) {
DEFINE1(sym);
*sym = intern(root, "t");
add_variable(root, env, sym, &True);
}
static void define_primitives(void *root, Obj **env) {
add_primitive(root, env, "quote", prim_quote);
add_primitive(root, env, "cons", prim_cons);
add_primitive(root, env, "car", prim_car);
add_primitive(root, env, "cdr", prim_cdr);
add_primitive(root, env, "setq", prim_setq);
add_primitive(root, env, "setcar", prim_setcar);
add_primitive(root, env, "while", prim_while);
add_primitive(root, env, "gensym", prim_gensym);
add_primitive(root, env, "+", prim_plus);
add_primitive(root, env, "-", prim_minus);
add_primitive(root, env, "<", prim_lt);
add_primitive(root, env, "define", prim_define);
add_primitive(root, env, "defun", prim_defun);
add_primitive(root, env, "defmacro", prim_defmacro);
add_primitive(root, env, "macroexpand", prim_macroexpand);
add_primitive(root, env, "lambda", prim_lambda);
add_primitive(root, env, "if", prim_if);
add_primitive(root, env, "=", prim_num_eq);
add_primitive(root, env, "eq", prim_eq);
add_primitive(root, env, "println", prim_println);
}
//======================================================================
// 入口点
//======================================================================
// 如果环境变量被定义且不为空字符串,则返回true。
static bool getEnvFlag(char *name) {
char *val = getenv(name);
return val && val[0];
}
int main(int argc, char **argv) {
// 调试标志
debug_gc = getEnvFlag("MINILISP_DEBUG_GC");
always_gc = getEnvFlag("MINILISP_ALWAYS_GC");
// 内存分配
memory = alloc_semispace();
// 常量和原始函数
Symbols = Nil;
void *root = NULL;
DEFINE2(env, expr);
*env = make_env(root, &Nil, &Nil);
define_constants(root, env);
define_primitives(root, env);
// 主循环
for (;;) {
*expr = read_expr(root);
if (!*expr)
return 0;
if (*expr == Cparen)
error("无关的闭合括号");
if (*expr == Dot)
error("无关的点标记");
print(eval(root, env, expr));
printf("\n");
}
}
开发、测试环境
win10
vs2022
linux虚拟机(centos7 gcc10.2 cmake3.10 …)
参考:https://blog.csdn.net/weixin_43172531/article/details/136191379
从上图可以看出,minilisp的编译运行测试没有任何问题。
minilisp独立运行测试方式(不用./test.sh)
方式1:echo “(+ 1 2)” | ./minilisp //echo "(+ 1 2)"会自动有一个EOF输出给minilisp
3 //程序输出结果3之后就退出了,没有交互过程
方式2:./minilisp <<EOF //触发出现交互命令提示符>
> (+ 1 2) //交互命令提示符> 及用户给出的lisp命令
> (+ 2 2)
> EOF //通知minilisp执行
3 //程序输出结果3之后就退出了
方式3:./minilisp
(+ 2 3) //回车触发minilisp执行
5 //minilisp输出的结果
c //随便敲一个不合法字符触发退出