#lisp #interop #scripting #run-time #embeddable #footprint #native

bin+lib rust_lisp

一个可嵌入 Rust 的 Lisp,支持与原生 Rust 函数的互操作

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编程语言

Download history 477/week @ 2024-04-22 401/week @ 2024-04-29 219/week @ 2024-05-06 408/week @ 2024-05-13 169/week @ 2024-05-20 112/week @ 2024-05-27 411/week @ 2024-06-03 239/week @ 2024-06-10 146/week @ 2024-06-17 215/week @ 2024-06-24 352/week @ 2024-07-01 325/week @ 2024-07-08 439/week @ 2024-07-15 247/week @ 2024-07-22 402/week @ 2024-07-29 251/week @ 2024-08-05

1,339 每月下载次数
3 库中使用 (2 个直接使用)

MIT 许可证

82KB
2K SLoC

GitHub Workflow Status rust_lisp shield docs.rs

这是什么?

这是一个用 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 }));

包含的功能

特殊形式:definesetdefundefmacrolambdaquoteletbegincondifandor

函数(在 default_env() 中):printis_nullis_numberis_symbolis_booleanis_procedureis_paircarcdrconslistnthsortreversemapfilterlengthrangehashhash_gethash_set+-*/truncatenot==!=<<=>>=applyeval

其他特性

  • 使用逗号转义符进行引用
  • Lisp 宏
  • 尾调用优化

依赖

~220KB