#binary-format #binary #format #language #compiler #protobuf

protospec

一种统治所有二进制格式的语言,一种寻找它们的语言,一种将它们全部带进来并在黑暗中绑在一起的语言

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编码

MIT 许可证

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

计划中的功能

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

无运行时依赖