4个版本 (2个破坏性版本)
0.3.0 | 2022年2月21日 |
---|---|
0.2.0 | 2022年2月17日 |
0.1.1 | 2021年3月27日 |
0.1.0 | 2021年3月27日 |
#1850 在 编码
2KB
ProtoSpec
目的
ProtoSpec受Google的ProtoBuf启发,但试图提供一个能够表示任何二进制格式的能力,而不是“安于其道”。
状态
ProtoSpec正在运行并进行了最小测试。一些功能尚未实现,编译器在泄漏/未记录的语义和崩溃方面仍然有些混乱。该项目是一个正在进行中的项目(当然,可能永远都会是这样)。欢迎贡献。
总体设计 & 术语
类型声明
ProtoSpec type
声明是protospec文件的顶级声明的主要形式。示例
type test = u32;
声明的类型是可编码和可解码的,并且可以同时解释为一系列函数声明和目标语言中的类型声明。
它们可以有条件,如果评估为 false
,则将类型声明编码为空字节数组。
它们可以有任意数量的转换。
参数
类型声明可以有任意数量的参数。示例
type example(compressed: bool) = container {
len: u32,
inner: container [len] {
data: [..]
} -> gzip {compressed},
};
参数在通过 encode_*
/decode_*
函数编码顶级类型时指定。当从另一个protospec类型调用时,它们通过类似函数调用的语法声明
type example_compressed = example(true);
常量声明
ProtoSpec const
声明是protospec文件额外的顶级声明。它可以用于存储相关和特定的常量。示例
const X: u32 = 1 + 2;
它们关联的类型不得有任何条件或转换。它们通常预期只包含原始类型。
导入声明
ProtoSpec import
声明可以通过相对路径导入其他ProtoSpec文件中声明的类型。将生成整个导入文件的代码。示例
import test_container from "test-import";
type test_impl = test_container[2];
枚举
ProtoSpec enum
类型只能定义为顶级类型(通过类型声明直接)。本质上,它与 const
声明相同,但在某些情况下可以在目标语言中更好地表示,并且在某些情况下使用起来更简洁。它们必须由标量(整数)表示类型支持。示例
type test = enum i32 {
west = 1,
east, // value of 2
north = 6,
south, // value of 7
};
ProtoSpec 由于表示/编码的歧义而没有标记联合。
容器
ProtoSpec 中的 container
类型是最强大的类型。它类似于结构体。容器包含相邻编码的字段,每个字段都有自己的名称、类型、条件和转换。容器可以嵌套其他容器。嵌套容器不能有条件,也不能是数组的内部元素。
字段在其列出的类型之后可以有任意数量的标志。目前声明的标志包括
+auto
:在编码时,任何声明的值都会被忽略,并使用容器长度约束来推断字段值。
示例
type test = container {
len: u32 +auto,
inner: container [len] {
tag: u8[32],
data: u8[..],
} -> gzip {is_compressed},
};
数组
ProtoSpec 数组类型是 ProtoSpec 中第二强大的类型。它们可以包含任何内部元素类型。数组本身可以根据其所有者/父类型具有转换和条件。未实现 内部类型可以包含转换或条件。
数组可以通过引用先前声明的字段、常量值或它们的组合来表示特定长度。例如
container {
ex1: u8[7],
ex2: u32,
ex3: u8[ex2],
}
数组可以通过 [..]
表示无界数组,它消耗所有可用数据。如果在解码内部类型时遇到流结束,则是一个错误。例如
container {
example: u8[..],
}
数组可以通过 [.."X"]
表示序列终止的无界数组,其中 X 是用于终止字符串的任何字符串序列。
container {
my_c_string: u8[.."\0"],
my_next_c_string: u8[.."\0"],
all_the_strings: u8[.."\0"][..],
}
外部类型
ProtoSpec 中的外部类型允许实现依赖的结构,可以表达 ProtoSpec 中不可能表达的事情。例如用法
import_ffi test_type as type;
type example = test_type[2];
示例实现
- 见
./src/prelude/var.rs
它们可能包括类似于类型声明的参数。
条件
ProtoSpec 中的条件是一种可选编码字段的机制。当字段条件为假时,它将不会被编码或解码。
示例
type test = container {
my_flags: u32,
alternative_u32: u32 {my_flags == 1},
alternative_u64: u64 {my_flags == 2},
}
转换
转换是一种强大的表示双向数据转换流的方法。它们可以接受多个参数,并且可以条件性地应用。它们仅通过 FFI 声明。
它们在序列化类型的评估中从左到右进行,在反序列化类型的评估中从右到左进行。
示例用法
import_ffi test_xform as transform;
type example = container {
ex1: u32 -> transform(7 /* argument */),
ex2: u32 -> transform {ex1 == 5 /* conditional transform */},
ex3: u32 -> transform(7) {ex2 == 3} -> transform(5),
} -> transform;
示例实现
- 见
./src/prelude/gzip.rs
支持的后端
- Rust
- 将
protospec_build
作为构建依赖项,并在build.rs
中调用protospec_build::compile_spec
。
然后使用fn main() { protospec_build::compile_spec("example_spec", include_str!("./spec/example_spec.pspec"), &protospec_build::Options { ..Default::default() }).expect("failed to build example_spec.pspec"); }
protospec::include_spec
或include!(concat!(env!("OUT_DIR"), "/example_spec.rs"))
将模块包含到您的项目中。 - 将
计划中的功能
- 类型泛型
- 支持 ASG 中的数组内部转换和条件
- 添加在转换中引用原始字段的能力
- 标志将可选字段编码为零
- 顶级字段依赖关系和字段重排序的 DCG
- 清理编码/解码
- 大量的文档
- 枚举默认值引用:
MyEnum::Default(5)
, - 匿名容器(推测存在
container
关键字)