24 个版本 (破坏性更新)
0.18.0 | 2022年12月24日 |
---|---|
0.16.1 | 2022年11月1日 |
0.15.0 | 2022年6月17日 |
0.7.2 | 2022年3月30日 |
0.1.0 | 2020年7月23日 |
#97 在 编程语言 中
1,339 每月下载次数
在 3 个 库中使用 (2 个直接使用)
82KB
2K SLoC
这是什么?
这是一个用 Rust 编写的 Lisp 解释器,旨在作为库嵌入到更大的应用程序中以进行脚本编写。目标
- 小体积(代码大小和内存使用量)
- 无运行时依赖 [1]
- 与原生 Rust 函数的简单、便捷的互操作
- 小而实用的 Lisp 功能集
[1] cfg-if
是构建时的,num-traits
我认为不会添加运行时存在,而 num-bigint
是完全可选的(在构建时)
基本用法
[dependencies]
rust_lisp = "0.18.0"
use std::{cell::RefCell, rc::Rc};
use rust_lisp::default_env;
use rust_lisp::parser::parse;
use rust_lisp::interpreter::eval;
fn main() {
// create a base environment
let env = Rc::new(RefCell::new(default_env()));
// parse into an iterator of syntax trees (one for each root)
let mut ast_iter = parse("(+ \"Hello \" \"world!\")");
let first_expression = ast_iter.next().unwrap().unwrap();
// evaluate
let evaluation_result = eval(env.clone(), &first_expression).unwrap();
// use result
println!("{}", &evaluation_result);
}
如你所见,基本环境由库的用户管理,解析阶段也是如此。这是为了给用户提供最大的控制权,包括通过 Result
的错误处理。
数据模型
模型的核心是 Value
,这是一个枚举,包含所有有效的 Lisp 值类型。其中大多数都是平凡的,但 Value::List
不是。它包含一个递归的 List
数据结构,在内部像链表一样工作。已为 List
实现了 into_iter()
和 from_iter()
,还有一个 lisp!
宏(见下文),这使得与列表(尤其是列表)的交互更加方便。
Value
没有实现 Copy
,因为存在类似 Value::List
的情况,所以如果您阅读源代码,会发现有很多 value.clone()
的调用。这几乎总是等于复制了一个原始值,除非在 Value::List
的情况下,它意味着克隆一个内部的 Rc
指针。在所有情况下,都认为这样做足够便宜,可以自由地进行。
环境和暴露 Rust 函数
基本环境由库的用户管理,主要是为了能够自定义。函数 default_env()
会预先填充环境中的许多常见函数,但您可以选择省略(或减少)这些函数。向环境添加条目也是您将 Rust 函数暴露给脚本的方式,这些脚本可以是常规函数或闭包
fn my_func(env: Rc<RefCell<Env>>, args: &Vec<Value>) -> Result<Value, RuntimeError> {
println!("Hello world!");
return Ok(Value::NIL);
}
...
env.borrow_mut().define(
Symbol::from("sayhello"),
Value::NativeFunc(my_func)
);
env.borrow_mut().define(
Symbol::from("sayhello"),
Value::NativeFunc(
|env, args| {
println!("Hello world!");
return Ok(Value::NIL);
})
);
在任意情况下,本地函数必须具有以下函数签名
type NativeFunc = fn(env: Rc<RefCell<Env>>, args: &Vec<Value>) -> Result<Value, RuntimeError>;
第一个参数是调用时的环境和位置(闭包作为环境扩展实现)。第二个参数是评估后的参数值 Vec。为了方便,已经提供了用于执行基本参数检索并带有错误消息的实用函数(如 require_arg()
、require_int_parameter()
等)。有关示例,请参阅 default_environment.rs
。
lisp!
宏
提供了一个名为 lisp!
的 Rust 宏,允许用户在 Rust 代码中嵌入清洗过的 Lisp 语法,这些语法将在编译时转换为 AST。
fn parse_basic_expression() {
let ast = parse("
(list
(* 1 2) ;; a comment
(/ 6 3 \"foo\"))").next().unwrap().unwrap();
assert_eq!(ast, lisp! {
(list
(* 1 2)
(/ 6 3 "foo"))
});
}
请注意,这只是为了给您一个语法树(以 Value
的形式)。如果您想实际评估表达式,则需要将其传递给 eval()
。
该宏还允许使用 { }
将 Rust 表达式(类型为 Value
)嵌入到 Lisp 代码中。
fn parse_basic_expression() {
let ast = parse("
(+ 3 1)").next().unwrap().unwrap();
let n = 2;
assert_eq!(ast, lisp! {
(+ { Value::Int(n + 1) } 1)
});
}
注意:在解析来自字符串的 Lisp 代码时,标识符中允许使用连字符(-
)。然而,由于声明式 Rust 宏的限制,这些无法被 lisp! {}
正确处理。因此,建议您在标识符中使用下划线,该宏将能够正确处理。内置函数遵循此约定。
注意 2:该宏无法处理负数的语法!为了解决这个问题,您可以使用转义语法将负数作为 Rust 表达式插入,或者将您的代码作为字符串解析。
值::外国()
有时,如果您想为现有系统编写脚本,可能不想将数据转换为与 Lisp 兼容的值。这既繁琐又低效。
如果您有一些类型(例如结构体),您想直接从 Lisp 代码中处理它,您可以将其放入 Value::Foreign()
中,这将允许 Lisp 代码传递它,并且(本地)Lisp 函数可以对其操作。
struct Foo {
some_prop: f32,
}
let v: Value = Value::Foreign(Rc::new(Foo { some_prop: 1.0 }));
包含的功能
特殊形式:define
、set
、defun
、defmacro
、lambda
、quote
、let
、begin
、cond
、if
、and
、or
函数(在 default_env()
中):print
、is_null
、is_number
、is_symbol
、is_boolean
、is_procedure
、is_pair
、car
、cdr
、cons
、list
、nth
、sort
、reverse
、map
、filter
、length
、range
、hash
、hash_get
、hash_set
、+
、-
、*
、/
、truncate
、not
、==
、!=
、<
、<=
、>
、>=
、apply
、eval
其他特性
- 使用逗号转义符进行引用
- Lisp 宏
- 尾调用优化
依赖
~220KB