#binary-format #binary #format #compiler #language #protospec

protospec-build

一种统治所有二进制格式语言的唯一语言,一种寻找它们,一种将它们全部召集,并在黑暗中束缚它们的唯一语言

2 个不稳定版本

0.3.0 2022年2月21日
0.2.0 2022年2月17日

#1483开发工具

MIT 许可证

295KB
8K SLoC

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 类型是 ProtoSpec 中最强大的类型。它与结构体类似。容器包含相邻编码的字段,每个字段都有自己的名称、类型、条件和转换。容器可以嵌套其他容器。嵌套容器不能有条件,也不能是数组的内部元素。

字段可以在其列出的类型后面有任意数量的标志。目前声明的标志包括

  • +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_specinclude!(concat!(env!("OUT_DIR"), "/example_spec.rs")) 将模块包含到项目中。

计划中的功能

  • 类型泛型
  • 支持在 ASG 中为数组内部添加转换和条件
  • 添加在转换中引用原始字段的能力
  • 将可选字段编码为零的标志
  • 顶级字段依赖关系和字段重排的 DCG
  • 清理编码/解码
  • 大量文档
  • 枚举默认值引用:MyEnum::Default(5)
  • 匿名容器(猜测存在 container 关键字)

依赖项

~2.9–4MB
~78K SLoC