带垃圾回收的Lisp解释器,用少于750行Odin(或少于500行C)实现。
Lisp interpreter with GC in <750 lines of Odin (and <500 lines of C)

原始链接: https://github.com/krig/LISP

这个项目实现了一个LISP 1.5解释器,灵感来自开创性的论文“符号表达式的递归函数及其机器计算”。它是一个单文件实现,代码量少于600行,旨在兼容Scheme(尽管这可能随着更新而改变)。 提供了两个版本:一个用C编写(`komplott.c`),另一个是翻译成Odin编程语言(`komplodin.odin`),Odin版本在初步翻译后正在进一步完善。两者都使用基于Cheney算法的半空间垃圾收集器。 该解释器具有有限的尾调用优化和最少的错误处理/安全性。它包括核心LISP 1.5功能并可以执行测试用例。构建需要`make`以及`gcc`(用于C)或Odin编译器。输入读取直到遇到“STOP”,然后跟随大量闭合括号以表示输入结束。

## Odin Lisp 解释器:一个紧凑的实现 一位开发者 (krig) 使用不到 750 行 Odin 代码,甚至更少,不到 500 行 C 代码,创建了一个带有垃圾回收的 Lisp 解释器。 该项目最初是 Odin 的一个学习练习,开发者现在更喜欢 Odin,因为它在保持相似感觉的同时有所改进。 Odin 的主要特点包括缺乏未定义行为、改进的内存管理(尽管仍然是手动管理),以及完善的语言状态,从而最大限度地减少了破坏性更改。 一个特别有趣的方面是半空间垃圾收集器,它以简洁的方式实现。 评论区的讨论涉及 Odin 的设计选择、与 Rust 的比较(特别是关于安全性),以及创建者 (Ginger Bill) 有时粗暴的沟通方式。 尽管该解释器是一个“玩具”项目,但它展示了 Odin 中简洁实现的可能性,并引发了关于未定义行为和语言设计哲学的争论。 该项目还遇到了问题,公司防病毒软件错误地将 `.odin` 文件标记为勒索软件。
相关文章

原文

A tribute to:

Recursive Functions of Symbolic Expressions and Their Computation by Machine, Part I

(as found in paper/recursive.pdf)

A micro-subset of scheme / the original LISP in a single C file: komplott.c

The LISP interpreter translated to Odin in komplodin.odin. More lines of code, but I am less familiar with the language and am translating directly from C, so there are probably ways to make it a cleaner solution.

When I posted this to lobste.rs, gingerBill (creator of Odin) was kind enough to make a more direct translation of the C code into Odin, which can be viewed in this gist: komplott.odin.

Since the lobste.rs posting, I have tweaked the Odin version a bit more, and so it differs from the C version quite a bit in the implementation details. I've tried to keep the output and functionality of the two programs the same though.

  • Single file implementation.

  • Less than 500 lines of code (~600 lines for the Odin version)

  • Scheme-compliant enough for the test programs to be executable by GNU Guile (not sure if this is true anymore)

  • Copying semi-space garbage collector based on Cheney's Algorithm.

    For more details on how it works, Andy Wingo has a great post about this kind of garbage collector on his blog (wingolog).

  • Limited tail call optimization (not true TCO; see tests/true-tco.scm).

  • Near-zero error handling.

  • Zero thread safety or security.

Also includes:

An implementation of the core of LISP 1.5 from 1962

  • To build the komplott executable, run make komplott. The only dependency aside from make is gcc.

  • To build the Odin version (komplodin), run make komplodin. This depends on the Odin compiler.

  • To run the LISP 1.5 interpreter and a couple of test cases, run make test.

The version presented in the README is slightly tweaked from the one that can be found in tests/lisp15.scm in order to more closely resemble early LISP rather than scheme: #t and #f are written as t and nil.

(define pairlis (lambda (x y a)
                  (cond ((null? x) a)
                        (t (cons (cons (car x) (car y))
                                 (pairlis (cdr x) (cdr y) a))))))

(define assoc (lambda (x a)
                (cond ((equal? (caar a) x) (car a))
                      (t (assoc x (cdr a))))))

(define atom? (lambda (x)
                (cond
                 ((null? x) t)
                 ((atom? x) t)
                 (t nil))))

(define evcon (lambda (c a)
                (cond
                 ((eval (caar c) a) (eval (cadar c) a))
                 (t (evcon (cdr c) a)))))

(define evlis (lambda (m a)
                (cond
                 ((null? m) nil)
                 (t (cons (eval (car m) a)
                             (evlis (cdr m) a))))))

(define apply (lambda (fun x a)
                (cond
                 ((atom? fun)
                  (cond
                   ((equal? fun (quote CAR)) (caar x))
                   ((equal? fun (quote CDR)) (cdar x))
                   ((equal? fun (quote CONS)) (cons (car x) (cadr x)))
                   ((equal? fun (quote ATOM)) (atom? (car x)))
                   ((equal? fun (quote EQ)) (equal? (car x) (cadr x)))
                   (t (apply (eval fun a) x a))))

                 ((equal? (car fun) (quote LAMBDA))
                  (eval (caddr fun) (pairlis (cadr fun) x a)))

                 ((equal? (car fun) (quote LABEL))
                  (apply
                   (caddr fun)
                   x
                   (cons
                    (cons (cadr fun) (caddr fun))
                    a))))))

(define eval (lambda (e a)
               (cond
                ((atom? e) (cdr (assoc e a)))
                ((atom? (car e))
                 (cond
                  ((equal? (car e) (quote QUOTE)) (cadr e))
                  ((equal? (car e) (quote COND)) (evcon (cdr e) a))
                  (t (apply (car e) (evlis (cdr e) a) a))))
                (t (apply (car e) (evlis (cdr e) a) a)))))

(define evalquote (lambda (fn x) (apply fn x (quote ()))))

Here is an example of actual LISP 1.5 code:

((LABEL MAPCAR
        (LAMBDA (FN SEQ)
                (COND
                  ((EQ NIL SEQ) NIL)
                  (T (CONS (FN (CAR SEQ))
                           (MAPCAR FN (CDR SEQ)))))))
 DUP LST)

; where
; DUP -> (LAMBDA (X) (CONS X X))
; LST -> (A B C)

To prevent reading from continuing indefinitely, each packet should end with STOP followed by a large number of right parentheses. An unpaired right parenthesis will cause a read error and terminate reading.

STOP )))))))))))))))))

联系我们 contact @ memedata.com