#垃圾回收 #类型检查器 #方案解释器

kamo

一个库,用于创建解释器或编译器及其关联的运行时

15 个版本 (8 个破坏性更改)

0.9.4 2024年5月26日
0.8.1 2024年4月16日
0.6.0 2024年3月26日
0.4.0 2023年12月27日
0.1.1 2023年11月27日

#47 in 内存管理

Download history 463/week @ 2024-04-25 30/week @ 2024-05-02 1/week @ 2024-05-16 150/week @ 2024-05-23 15/week @ 2024-05-30 5/week @ 2024-06-06 2/week @ 2024-06-13

每月1,145次下载

MIT/Apache

1MB
15K SLoC

卡莫

Build Status Crates.io Crates.io

卡莫(カモ)是日语中鸭子的意思。

  • 这是 Kamo 项目的第三部分发布:类型系统和类型检查器。当启用 types 功能时,类型系统可用。默认情况下未启用。当启用类型系统时,env 模块也可用。它由 TypeChecker 使用。

  • 这是 Kamo 项目的第二部分补充:内存管理和运行时值。它向 kamo::form::sexpr 模块添加了 s-表达式的运行时解析器。有关更改和修复的更多信息,请参阅变更日志。

  • 这是 Kamo 项目的第二部分补充:内存管理和运行时值。它添加了将 s-表达式解析为运行时值的进程宏。这些宏定义在 crate kamo-macros 中。

  • 这是 Kamo 项目的第二部分发布:内存管理和运行时值。

  • 第一部分是解析器组合库。

内存管理模块 kamo::mem

此模块实现了自动内存管理。值在持有一个每个类型都持有竞技场分配器的突变器中分配。内存收集由标记和清除垃圾收集器完成。

突变器实现是线程局部。突变器持有每个类型的竞技场分配器的向量。竞技场分配器实现为桶的向量。每个桶包含一个槽的向量。槽用于存储值。桶具有固定的大小并在堆上分配。当槽满时,根据需要分配桶。

类型的层次结构如下

  • 突变器包含竞技场。
  • 竞技场包含桶。
  • 桶包含槽。
  • 槽包含一个值。
  • 值由指针表示。

由突变器管理的值必须是可追踪的。突变器使用 Trace 特性来追踪值。对于所有可以包含突变器管理的值的类型,都实现了 Trace 特性。

指针使用引用计数来跟踪对值的引用数量。当引用数量达到零时,值可能会被垃圾回收。为了避免循环,Pair(也称为 Cons 单元)和 Vector 不会对其元素持有锁。相反,在内存收集时由突变器追踪。这就是为什么指针的引用计数减少到零并不会立即释放值的原因。

垃圾回收器实现为标记和清除收集器。所有持有锁的值都是根。垃圾回收器追踪所有根,并标记所有从根可到达的值。所有未标记的值都是不可到达的,并会被释放。通过在值上反复调用 trace 方法并将值添加到工作列表中,迭代地执行追踪。这会持续到工作列表为空。

std::alloc::Allocator 特性

突变器使用全局分配器为桶、竞技场、向量和字符串等分配内存。动态内存分配由 std::alloc 链接库提供的分配器执行。由于标准库提供的分配器实现高度优化,重新实现它将是徒劳的。

因此,当全局分配器无法分配内存时,突变器无法释放内存。这是因为突变器不知道哪些内存是由全局分配器分配的。突变器只知道哪些内存是由其竞技场分配的。内存收集仅由分配压力触发。

当分配器特性稳定后,突变器将能够使用该特性,通过在全局分配器失败时释放内存来减轻分配失败。

运行时值模块 kamo::value

此模块实现了运行时值。值可以持有立即值或指向突变器中分配的值的指针。

支持以下立即值:

  • nil,空列表
  • bool
  • char,一个 Unicode 标量值
  • i64
  • f64

支持以下指针值:

  • pair,一对值,也称为 Cons 单元
  • symbol,一个符号,一个内部字符串
  • string,一个 UTF-8 字符串
  • vector,一个值向量
  • bytevector,一个字节向量

值提供了一个安全的接口来访问运行时的值。它作为 ValueKind 的包装器实现。它提供了将值转换为 Rust 类型、检查值的类型和访问值的方法。堆值在必要时自动锁定和解锁。

值实现了访问者模式。使用 Visitor 特性来访问值。在实现 Visitor 特性时,必须考虑值可能是循环的。访问者必须跟踪它已经访问过的值,以避免无限循环。

《Visitor》特性用于实现值的打印。本身《Value》没有实现打印。而是为特定的访问者实现了《Display》特性。这使得可以为值实现不同的打印策略。

提供了一个《Visitor》特性的实现,用于以类似于Scheme的语法打印值。

解析器组合库 kamo::parser

此模块实现了解析器组合库。该库专注于以安全和零拷贝的方式解析UTF-8文本。它被设计用于实现编程语言的解析器。它不是为解析二进制数据而设计的。

此库的一个设计目标是使其易于编写能够跟踪输入位置和错误原因的解析器。位置通过从输入开始的字节偏移量和UTF-8字符的行和列号自动跟踪。错误原因通过错误中的原因栈进行跟踪。该栈用于跟踪单个错误的多个原因。当解析器失败时,原因被添加到栈中,并包含位置、错误代码和消息。错误代码用于识别错误的原因。通常,输入解析器失败时,将原因添加到栈中的解析器会将原因添加到栈中。

示例

以下是一个解析类似Scheme的字节向量并将其作为Vec<u8>返回的解析器示例。

语法定义如下

ByteVector = "#u8(" Byte* ')'
Byte       = <any exact integer between 0 and 255>

解析器定义如下

use kamo::parser::{code, prelude::*, Input, ParseError, ParseResult, Span};

fn main() {
    assert_eq!(
        parse_bytevec(Input::new("#u8(0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15)")),
        Ok((
            vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
            Input::new("")
        ))
    );
}

fn parse_bytevec(input: Input<'_>) -> ParseResult<'_, Vec<u8>> {
    context_as(
        delimited(
            pair(tag("#u8("), ascii::whitespace0),
            list0(parse_bytevec_value, ascii::whitespace1),
            context_as(
                preceded(ascii::whitespace0, char(')')),
                code::ERR_CUSTOM + 2,
                "expecting a closing parenthesis: )",
            ),
        ),
        code::ERR_CUSTOM + 1,
        "expecting a byte vector: #u8(<byte>*)",
    )(input)
}

fn parse_bytevec_value(input: Input<'_>) -> ParseResult<'_, u8> {
    let result = preceded(
        ascii::whitespace0,
        any((
            parse_binary_natural,
            parse_octal_natural,
            parse_decimal_natural,
            parse_hexadecimal_natural,
            parse_natural,
        )),
    )(input);

    match result {
        Ok((value, cursor)) => {
            if value > u8::MAX as u64 {
                Err(ParseError::new(
                    Span::new(input.position(), cursor.position()),
                    code::ERR_CUSTOM + 3,
                    "expecting a byte value: 0..255",
                ))
            } else {
                Ok((value as u8, cursor))
            }
        }
        Err(err) => Err(err),
    }
}

#[inline]
fn parse_binary_natural(input: Input<'_>) -> ParseResult<u64> {
    preceded(tag("#b"), literal::natural(literal::Radix::Binary))(input)
}

#[inline]
fn parse_octal_natural(input: Input<'_>) -> ParseResult<u64> {
    preceded(tag("#o"), literal::natural(literal::Radix::Octal))(input)
}

#[inline]
fn parse_decimal_natural(input: Input<'_>) -> ParseResult<u64> {
    preceded(tag("#d"), parse_natural)(input)
}

#[inline]
fn parse_hexadecimal_natural(input: Input<'_>) -> ParseResult<u64> {
    preceded(tag("#x"), literal::natural(literal::Radix::Hexadecimal))(input)
}

#[inline]
fn parse_natural(input: Input<'_>) -> ParseResult<u64> {
    literal::natural(literal::Radix::Decimal)(input)
}

类型系统

当启用《types》功能时,类型系统可用。默认情况下没有启用。当启用类型系统时,《env》模块也可用。它被《TypeChecker》使用。

类型通过类型代码定义,类型代码是一个字节数组。类型代码用于表示值的类型。这允许以简单有效的方式表示类型。类型代码的语法简单易懂,易于解析。类型代码允许通过组合更简单的类型构建具有多级嵌套的复杂类型。

支持的基本类型如下

  • 布尔型:布尔值。
  • 字符:Unicode字符。
  • 整数:有符号整数值。
  • 浮点型:浮点值。
  • 符号:内部字符串值。
  • 类型:类型值。
  • 二进制:字节数组值。

除了基本类型外,类型系统还支持以下复合类型

  • 数组:同构元素集合。
  • :两个值的对,也称为cons单元。
  • Lambda:函数类型。
  • Option:可以是《None》或《Some(T)》的类型。
  • Union:可以是给定类型之一的数据类型。联合中必须至少有两个类型。

还支持特殊类型

  • Any:可以是任何值的类型。
  • Void:不能是任何类型或值的类型。它用于表示类型的缺失。它用于表示不返回值或不需要任何参数的函数的上下文中。
  • Nil:表示值缺失的类型。它在可选类型上下文中使用。

类型被分组到不同的类别中

  • 特定类型:代表特定值并且可以是联合类型成员的类型。以下是一些特定类型:布尔型字符型整型浮点型符号型类型型二进制型数组型对型lambda
  • 填充类型:代表更通用的特定值。这些类型可以用在选项类型中。以下是一些填充类型:AnyUnion特定类型

解析器

类型系统包括两个解析器:类型代码解析器和类型文本表示解析器。类型代码解析器用于解析类型代码并将它们转换为类型。类型文本表示解析器用于解析字符串并将它们转换为类型。类型的文本表示是一种人类可读格式,可以用来以比类型代码更可读的方式表示类型。

类型检查器

TypeChecker特质用于实现解释器或编译器的类型检查。类型检查器用于检查一个值是否具有特定的类型或者一个类型是否是另一个类型的子类型。类型检查器还可以用于推断表达式或值的类型。类型检查器用于实现编程语言中的类型推断和类型检查。

宏用于将字符串字面量解析为单个或多个kamo::value::Value值。宏定义如下

  • sexpr!(<mutator>, <expression>) - 用于从字符串解析单个s-expressions的宏。它返回一个kamo::value::Value

  • sexpr_file!(<mutator>, <filename>) - 用于从文件解析多个s-expressions的宏。它返回一个kamo::value::Value值的数组。该数组可能为空。

  • sexpr_script!(<mutator>, <expressions>) - 用于从字符串解析多个s-expressions的宏。它返回一个kamo::value::Value值的数组。该数组可能为空。

所有宏都接受一个可选的MutatorRef标识符。这用于在堆上分配值。如果表达式不包含需要在堆上分配的任何值,则可以省略Mutator标识符。

宏的语法由Scheme标准R7RS中的read过程定义。语法定义在标准的第"7.1.2 外部表示法"部分中。

与标准的语法偏差如下

  • 不支持数字的精确度(#e#i)。浮点数总是不精确的,整数总是精确的。

  • 数字只能是带有符号的64位整数或IEEE 754双精度浮点数。标准允许任意精度的整数、有理数和复数。

  • 不支持标签。

  • #; 注释语法仅在解析多个s-expressions的宏中受支持。#; 注释语法不能嵌套。

  • 由十六进制转义序列定义的字符字面量可以有1到6位数字。标准期望至少有1位数字。代码必须是有效的Unicode码点。

解析器使用 pest crate 实现。语法定义在 src/sexpr/sexpr.pest。这是必要的,因为在此处无法使用在 kamo::parser 中定义的解析器库。这会导致循环依赖。将来将在 kamo::form 模块中实现s-expressions解析器的实现。它将基于 kamo::parser crate,并将被scheme解释器使用。

示例

use kamo::{mem::Mutator, sexpr, value::{print, Value}};
 
let m = Mutator::new_ref();
let value = sexpr!(m, "(1 2 3)");
 
assert_eq!(print(value).to_string(), "(1 2 3)");
use kamo::{sexpr_file, value::{print, Value}};
 
let m = Mutator::new_ref();
let values = sexpr_file!("tests/sexpr/values.scm");
 
assert_eq!(values.len(), 3);
assert_eq!(print(values[0].clone()).to_string(), "()");
assert_eq!(print(values[1].clone()).to_string(), "100");
assert_eq!(print(values[2].clone()).to_string(), "#t");

let values: &[Value] = &sexpr_file!("tests/sexpr/empty.scm");
assert_eq!(values.len(), 0);
use kamo::{mem::Mutator, sexpr_script, value::{print, Value}};
 
let m = Mutator::new_ref();
let values = sexpr_script!(m, "(define a 1)\n(define b 2)\n(+ a b)");
 
assert_eq!(values.len(), 3);
assert_eq!(print(values[0].clone()).to_string(), "(define a 1)");
assert_eq!(print(values[1].clone()).to_string(), "(define b 2)");
assert_eq!(print(values[2].clone()).to_string(), "(+ a b)");

let values: &[Value] = &sexpr_script!("");
 
assert_eq!(values.len(), 0);

特性列表

  • 模块 kamo::parser 用于解析UTF-8文本。一个用于安全且零拷贝方式解析UTF-8文本的解析器组合库。
  • 模块 kamo::mem 用于自动内存管理。值在持有每个类型的竞技场的分配器的mutator中分配。内存收集由标记和清除垃圾回收器完成。
  • 模块 kamo::value 用于值。值可以持有立即值或指向在mutator中分配的值的指针。
  • 模块 kamo::types 用于类型。类型系统用于推断中间表示和AST的类型。
  • 模块 kamo::eval 用于评估。评估器处理AST(符号表达式树),并将其评估为中间表示。中间表示可以随后被解释或编译为目标表示。解释器是通用的,可以用于解释任何中间表示。
  • 模块 kamo::repl 用于读取-评估-打印循环。REPL用于交互式评估表达式,是通用的,可以用于评估任何中间表示。
  • 模块 kamo::lang::scheme 用于Scheme语言。Scheme语言作为库在 kamo 模块之上实现。它实现了R7RS标准的子集。

依赖项

~0.3–1.5MB
~26K SLoC