c编译器学习05:与chibicc类似的minilisp编译器(待修订 ...)

news2024/9/21 14:42:33

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语法也是比较简单。

暂时留个坑。
后面再填。

minilisp语法

待修订…

c是否必须mini lisp
运算符************************************************************************************************************************
算术运算符+ -*/%+ - * /
关系运算符== != > < >= <== < > <= >=
逻辑运算符&& || !and, or, not
位运算符& | ^ ~ << >>N/A
赋值运算符=let
运算符优先级用()改变求值顺序。()
数据************************************************************************************************************************
keysvoid*,unsigned,char,int,long,double,structatoms, 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
控制************************************************************************************************************************
keysif,else,goto,do,while,break,continue,returnif, cond, quote, lambda, define
分支控制if(…){goto}else{goto} 对应图灵机的磁头左右移动。为了方便编程,保留if 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-6464位 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命令
> EOF //通知minilisp执行
3 //程序输出结果3之后就退出了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1464684.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Docker Nginx 负载均衡搭建(服务宕机-配置高可用) - 附(Python案例,其它语言同理)

目录 一 . 概要 1. 什么是负载均衡 2. 负载均衡有哪些优势&#xff1f; &#xff08;1&#xff09;应用程序可用性 &#xff08;2&#xff09;应用程序可扩展性 &#xff08;3&#xff09;应用程序安全 &#xff08;4&#xff09;应用程序性能 3 . Nginx负载均衡调度策…

openGauss学习笔记-226 openGauss性能调优-系统调优-配置LLVM-LLVM适用场景与限制

文章目录 openGauss学习笔记-226 openGauss性能调优-系统调优-配置LLVM-LLVM适用场景与限制226.1 适用场景226.2 非适用场景 openGauss学习笔记-226 openGauss性能调优-系统调优-配置LLVM-LLVM适用场景与限制 226.1 适用场景 支持LLVM的表达式 查询语句中存在以下的表达式支持…

uniapp开发微信小程序跳转到另一个小程序中

注意&#xff1a;一开始我的云上务工模块是单独的tabbar界面&#xff0c;但是小程序跳转好像不能直接点击tabbar进行&#xff0c;所以我将这里改成了点击首页中的按钮进行跳转 点击这里进行小程序跳转 目录 基础讲解 uniapp小程序跳转的两个方法 调用说明&#xff08;半屏跳转…

【鸿蒙 HarmonyOS 4.0】ArkTS开发语言

一、背景 ArkTS是HarmonyOS优选的主力应用开发语言。ArkTS围绕应用开发在TypeScript&#xff08;简称TS&#xff09;生态基础上做了进一步扩展&#xff0c;继承了TS的所有特性&#xff0c;是TS的超集。 二、基本语法 2.1、基本语法介绍 ArkTS的基本组成&#xff0c;资料来自…

企业安全建设工具推荐

全自动化挖洞&#xff0c;助力企业安全建设&#xff0c;一键实现域名扫描、IP 发现、端口扫描、服务识别、网站识别、漏洞探测、分析发现、合规检查。 使用方式&#xff1a; 录入目标企业名称即可开始使用 技术细节&#xff1a; 第一步&#xff1a;通过企业主体关联企业备案…

前端学习——JS学习

文章目录 1. 定义变量&#xff0c;关键字 var、let、const2. 定义变量&#xff0c;数据类型3. 数组变量的操作4. 对象的操作5. JSON 字符串 1. 定义变量&#xff0c;关键字 var、let、const 这里主要是对var、let做比较 /** 1. var存在变量提升、let不存在变量提升 **/ cons…

办理布基纳法索(Burkina Faso)ECTN认证

布基纳法索ECTN认证 一. 什么是布基纳法索&#xff08;Burkina Faso&#xff09;ECTN认证? 根据布基纳法索&#xff08;Burkina Faso&#xff09;签发于 11/07/2006法令编号 00557的规定: 自2006年11月07 日起所有出口至布基纳法索&#xff08;Burkina Faso&#xff09;的货…

ChatGPT调教指南 | 咒语指南 | Prompts提示词教程(一)

在我们开始探索人工智能的世界时&#xff0c;了解如何与之有效沉浸交流是至关重要的。想象一下&#xff0c;你手中有一把钥匙&#xff0c;可以解锁与OpenAI的GPT模型沟通的无限可能。这把钥匙就是——正确的提示词&#xff08;prompts&#xff09;。无论你是AI领域的新手&#…

Vue响应式状态ref()与reactive()

1. ref()声明响应式状态 <template><!--在DOM元素调用变量时,不需要指定输出变量的value,因为Vue会帮你输出.value但是注意,这个帮助只会帮助顶级的ref属性才会被解包--><div>{{ count }}</div><div>{{ object }}</div><div>{{ arr…

vue Threejs实现任意画线(鼠标点击画线)

Threejs实现任意画线(鼠标点击画线) 鼠标左键单击添加点鼠标右键回退到上一个点,并继续画按住shift可以画平行于x轴或平行于z轴的线按Esc完成画线

如何把视频转换成mp4格式?总结4种转换方法【全】

随着视频在我们生活中的普及&#xff0c;我们常常需要将视频文件转换成不同的格式以适应不同的播放设备或应用场景。而MP4格式作为一种广泛兼容且高质量的视频格式&#xff0c;常常是人们的首选。因此&#xff0c;掌握将视频转换成MP4格式的方法变得至关重要。 在本文中&#…

消息队列MQ 保证消息不丢失(消息可靠性)

文章目录 概述RabbitMQ 怎么避免消息丢失&#xff08;可靠传输&#xff09;RocketMQ 怎么确保消息不丢失Kafka 怎么保证消息不丢失activeMQ 怎么避免消息丢失MQ 宕机了消息是否会丢失线上服务宕机时&#xff0c;如何保证数据100%不丢失吗&#xff1f;消息队列消息持久化 概述 …

如何做bug分析 ?bug分析什么 ? 为什么要做bug分析 ?

每当我们完成一个版本测试时&#xff0c;总会在测试报告中添加一些分析bug的指标 &#xff0c;主要用于分析在测试过程中存在的问题 。但是在分析的过程中你就可能遇到如下的问题 &#xff1a; 我应该分析那些指标呢 &#xff1f;每一个具体的指标该如何分析 &#xff1f;它能说…

软件性能测试和功能测试有何联系和区别?第三方软件检测机构简析

软件性能测试和功能测试是软件开发过程中非常重要的两个环节。从根本上说&#xff0c;它们都是为了保证软件质量和可靠性&#xff0c;但它们的目标和方法却有所不同。 软件性能测试是评估软件在特定负载下的性能表现&#xff0c;包括响应时间、吞吐量、并发能力等指标。它通过…

1.deeplabv3+网络结构及原理

这里的网络结构及原理可以看这篇博客&#xff0c;DeepLabV3: 在DeepLabV3基础上引入了Decoder_matlab deeplabv3resnet101-CSDN博客该博客翻译原论文解释得很清楚。 一、引言 语义分割的目标是为图像中的每个像素分配语义标签。在这项研究中&#xff0c;考虑了两种类型的神经网…

Flink ML 的新特性解析与应用

摘要&#xff1a;本文整理自阿里巴巴算法专家赵伟波&#xff0c;在 Flink Forward Asia 2023 AI特征工程专场的分享。本篇内容主要分为以下四部分&#xff1a; Flink ML 概况在线学习的设计与应用在线推理的设计与应用特征工程算法与应用 一、Flink ML 概况 Flink ML 是 Apache…

Python自动化UI测试之Selenium基础实操

1. Selenium简介 Selenium 是一个用于 Web 应用程序测试的工具。最初是为网站自动化测试而开发的&#xff0c;可以直接运行在浏览器上&#xff0c;支持的浏览器包括 IE&#xff08;7, 8, 9, 10, 11&#xff09;&#xff0c;Mozilla Firefox&#xff0c;Safari&#xff0c;Googl…

(done) 矩阵的对角化,以及是否可对角化的判断、还有对角化的本质。相似对角化计算过程

相似对角化 和 对角化 很大程度上是一回事 甚至判断两个矩阵的相似性&#xff0c;也跟对角化有很大关系 参考视频1&#xff1a;https://www.bilibili.com/video/BV1PA411T7b5/?spm_id_from333.788&vd_source7a1a0bc74158c6993c7355c5490fc600 参考视频2&#xff1a;http…

node 之 初步认识

思考&#xff1a;为什么JavaScript可以在浏览器中被执行 代执行的js代码——JavaScript解析引擎 不同的浏览器使用不同的JavaScript解析引擎 Chrome 浏览器 》 V8 Firefox浏览器 》OdinMonkey(奥丁猴&#xff09; Safri浏览器 》JSCore IE浏览器 》Chakra(查克拉&#xff09; e…

【算法分析与设计】

&#x1f4dd;个人主页&#xff1a;五敷有你 &#x1f525;系列专栏&#xff1a;算法分析与设计 ⛺️稳中求进&#xff0c;晒太阳 题目 编写一个函数&#xff0c;输入是一个无符号整数&#xff08;以二进制串的形式&#xff09;&#xff0c;返回其二进制表达式中数字位…