#语言 #描述 #格式 #序列化 #Scala #文件格式 #运行时

verilization-lang-scala

Scala 语言对 verilization 描述语言的支持

1 个不稳定版本

0.1.0 2021 年 5 月 5 日

#2817解析器实现


用于 verilization-compiler-cli

GPL-3.0-only

130KB
3.5K SLoC

Verilization

Verilization 是一种序列化描述语言,旨在定义二进制文件格式。与 Protocol Buffers 等其他序列化工具不同,序列化的 Verilization 数据不具备向前或向后兼容性。相反,从旧格式版本转换变得容易,允许数据更加紧凑,并能够更好地控制数据的底层结构。

目标

Verilization 具有以下主要目标。

  • 给予用户对文件格式的最大控制
  • 以语言无关的方式定义格式
  • 支持从旧格式版本轻松转换

其他更低层次的目标。

  • 支持大整型类型
  • 无需使用本地二进制即可嵌入到其他语言中

类型

以下类型得到支持。

类型 编码
struct 类型 按照顺序编码每个字段
enum 类型 一个标签(与 nat 的编码格式相同)后面跟标签表示的字段的编码
extern 类型 由目标语言编写的代码定义

结构体

使用多个 版本 定义 struct 类型。每个版本定义一个字段列表。

struct Rectangle {
    version 1 {
        width: u32;
        height: u32;
    }
}

枚举

使用多个 版本 定义 enum 类型。每个版本定义一个用作情况的字段列表。枚举值正好由这些字段中的一个组成。

struct StringOrInt {
    version 1 {
        str: string;
        num: int;
    }
}

外部

在用户代码中定义 extern 类型。类型定义、转换和编解码器必须在目标语言中实现。

extern 类型可以声明可用于该类型的字面量。

extern MyString {
    literal {
        string;
    }
}

以下字面量规范得到支持。

名称 示例 语法 注意
整数 integer [0, 256) 'integer'open_bracket integer_literal? ','integer_literal?关闭括号
其中 `open_bracket : '['
'('andclose_bracket : ']'
字符串 字符串 '字符串' 字符串的内容不能被限制。
序列 序列 T '序列'类型表达式 定义一个指定类型的序列。
情况 情况 正数() '情况'标识符'(' [类型表达式{ ','类型表达式} ] ')' 定义一个情况。如果名称不同,则可以指定多个情况字面量。
记录 记录{a:A;b:B; } '记录' '{' {标识符':'类型表达式';' } '}' 定义一个记录。

运行时库类型

运行时库提供了一些 extern 类型。

类型 字面量 编码
{i,u}{8,16,32,64} 类型范围内的整数 小端顺序的字节固定宽度序列
整型 整数 可变长度的格式
nat 非负整数 int 类似的格式,但没有符号位
字符串 字符串 一个长度 nat,后跟指定长度的 UTF-8 字节序列
list T 类型 T 的序列 一个长度 nat,后跟一个 T 序列
option T 两种情况 some(x)none() 一个字节 b。如果 b 非零,则其后跟一个 T

intnat 的编码定义了一个小端顺序的位序列。每个字节的最高位被设置,如果数字中有更多的字节。

此编码是一个字节序列 [B0, ..., Bn],其中当 i < n 时 Bi,7 = 1,且 Bn,7 = 0。这个字节序列等效于一个位序列 [B0,0, ... B0,6, ..., Bn-1,0, ..., Bn-1,6] = [b0, ..., bm-1],其中 m = 6n。本质上,位序列去除了用于确定序列何时结束的标志位,并将每个字节中的剩余位从最低位到最高位进行排序。位序列映射如下

  • 对于 int 类型,如果 bm-1 = 0,则 k = b0 * 20 + ... + bm - 2 * 2m-2
  • 对于 int 类型,如果 bm-1 = 1,则 k = -(b0 * 20 + ... + bm - 2 * 2m-2) - 1
  • 对于 nat 类型,k = b0 * 20 + ... + bm - 1 * 2m-1

版本控制

在下面的示例中,用户有一个用户名和出生日期。

struct Person {
    version 1 {
        name: Name;
        dob: Date;
    }
}

struct Name {
    version 1 {
        firstName: string;
        middleName: option string;
        lastName: string;
    }
}

但是,并不是每个人都有 2 或 3 个名字。为了适应这种情况,我们可以创建一个新的版本,允许任意数量的名字。

struct Name {
    version 1 {
        ...
    }
    version 2 {
        names: list string;
    }
}

Name 的此更改意味着在格式的第 2 版中,Personname 字段现在将使用 Name 的第 2 版。然而,由于 Person 没有直接更改,版本 2 是自动创建的。在生成的代码中,预期用户将提供可以将 Name 从版本 1 升级到版本 2 的代码。然而,无需提供升级 Person 的此类代码。 Person 可以通过其字段的升级代码自动升级。

最终

版本化的类型可以被声明为 final 来表示不会添加该类型的新版本。这限制类型为最后一个显式声明的版本,防止自动生成新版本。最终类型可能只包含最终类型或非版本化类型的字段。

final struct FormatVersion {
    version 1 {
        major: nat;
    }
}

泛型

泛型类型允许一个类型被参数化。

final struct Pair(A, B) {
    version 1 {
        left: A;
        right: B;
    }
}

常量

常量允许定义在生成的任何语言之间共享的值。

文字 示例 用法
整数 88 extern 类型与 integer 文字
字符串 "你好,世界" extern 类型与 string 文字
序列 [a,b,c] extern 类型与 sequence 文字
记录 {x= 1;y= 2; } struct 类型与 extern 类型与 record 文字
情况 名称(a) enum 类型与 extern 类型与 case Name 文字

命令行

Verilization 有命令行界面。以下选项被支持。

语言生成器

以下语言被支持。

编译器绑定

verilization 编译器是用 Rust 编写的。它可以编译成 WebAssembly 以在其它语言中使用。这有优点,即工具可以分发(例如,作为 NPM 包、独立 JAR 等),而不需要任何原生二进制文件。这些绑定暴露了可以直接从运行时使用的接口,以及仅依赖于相关运行时的命令行界面。

目前,以下运行时存在绑定。

  • Node

依赖

~2.5MB
~56K SLoC