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