TL-B 语言
TL-B(类型语言 - 二进制)用于描述类型系统、构造函数和现有功能。例如,我们可以使用 TL-B 方案构建与 TON 区块链关联的二进制结构。特殊的 TL-B 解析器可以读取方案,将二进制数据反序列化为不同的对象。TL-B 描述了 Cell
对象的数据方案。如果您不熟悉 Cells
,请阅读 Cell & Bag of Cells(BOC) 文章。
概述
我们将任何一组 TL-B 构造称为 TL-B 文档。一个 TL-B 文档通常包括类型的声明(即它们的构造函数)和功能组合子。每个组合子的声明都以分号 (;
) 结尾。
这是一个可能的组合子声明示例:


构造函数
每个等式的左侧描述了定义或序列化右侧指示类型的值的方式。这样的描述以构造函数的名称开始。


构造函数用于指定组合器的类型,包括在序列化时的状态。例如,当你想在向 TON 智能合约查询时指定一个 op
(操作码)的时候,也可以使用构造函数。
// ....
transfer#5fcc3d14 <...> = InternalMsgBody;
// ....
- 构造函数名称:
transfer
- 构造函数前缀代码:
#5fcc3d14
注意,每个构造函数名称后面紧跟一个可选的构造函数标签,例如 #_
或 $10
,它描述了用于编码(序列 化)所讨论的构造函数的位串(bitstring)。
message#3f5476ca value:# = CoolMessage;
bool_true$0 = Bool;
bool_false$1 = Bool;
每个等式的左侧描述了定义或序列化右侧指示类型的值的方式。这样的描述以构造函数的名称开始,如 message
或 bool_true
,紧接着是一个可选的构造函数标签,例如 #3f5476ca
或 $0
,它描述了用于编码(序列化)所讨论的构造函数的位。
构造函数 | 序列化 |
---|---|
some#3f5476ca | 从十六进制值序列化为 32 位 uint |
some#5fe | 从十六进制值序列化为 12 位 uint |
some$0101 | 序列化 0101 原始位 |
some 或 some# | 序列化 crc32(equation) | 0x80000000 |
some#_ 或 some$_ 或 _ | 不进行序列化 |
构造函数名称(此示例中的 some
)在代码生成中用作变量。例如:
bool_true$1 = Bool;
bool_false$0 = Bool;
类型 Bool
有两个标签 0
和 1
。代码生成伪代码可能看起来像:
class Bool:
tags = [1, 0]
tags_names = ['bool_true', 'bool_false']
如果你不想为当前构造函数定义任何名称,只需传递 _
,例如 _ a:(## 32) = 32Int;
构造函数标签可以用二进制(在美元符号后)或十六进制表示法(在井号后)给出。如果未明确提供标签,则 TL-B 解析器必须通过用 CRC32 算法散列定义此构造函数的“等式”文本,并带有 | 0x80000000
来计算默认的 32 位构造函数标签。因此,必须通过 #_
或 $_
明确提供空标签。
此标签将用于在反序列化过程中猜测当前位串的类型。例如,我们有 1 位位串 0
,如果我们告诉 TLB 将此位串解析为 Bool
类型,它将被解析为 Bool.bool_false
。
假设我们有更复杂的示例:
tag_a$10 val:(## 32) = A;
tag_b$00 val(## 64) = A;
如果我们在 TLB 类型 A
中解析 1000000000000000000000000000000001
(1 和 32 个零和 1)- 首先我们需要获取前两位来定义标签。在此示例中,前两位 10
代表 tag_a
。所以现在我们知道接下来的 32 位是 val
变量,在我们的示例中是 1
。一些“解析”的伪代码变量可能看起来像:
A.tag = 'tag_a'
A.tag_bits = '10'
A.val = 1
所有构造函数名称必须是不同的,并且同一类型的构造函数标签必须构成一个前缀码(否则反序列化将不是唯一的);即,同一类型中的任何标签都不能是任何其他标签的前缀。
每种类型的构造函数最大数量:64
标签的最大位数:63
example_a$10 = A;
example_b$01 = A;
example_c$11 = A;
example_d$00 = A;
代码生成伪代码可能看起来像:
class A:
tags = [2, 1, 3, 0]
tags_names = ['example_a', 'example_b', 'example_c', 'example_d']
example_a#0 = A;
example_b#1 = A;
example_c#f = A;
代码生成伪代码可能看起来像:
class A:
tags = [0, 1, 15]
tags_names = ['example_a', 'example_b', 'example_c']
如果使用 hex
标签,请记住,它将以每个十六进制符号 4 位进行序列化。最大值是 63 位无符号整数。这意味着:
a#32 a:(## 32) = AMultiTagInt;
b#1111 a:(## 32) = AMultiTagInt;
c#5FE a:(## 32) = AMultiTagInt;
d#3F5476CA a:(## 32) = AMultiTagInt;
构造函数 | 序列化 |
---|---|
a#32 | 从十六进制值序列化为 8 位 uint |
b#1111 | 从十六进制值序列化为 16 位 uint |
c#5FE | 从十六进制值序列化为 12 位 uint |
d#3F5476CA | 从十六进制值序列化为 32 位 uint |
十六进制值允许使用大写和小写。
关于十六进制标签的更多信息
除了经典的十六进制标签定义外,十六进制数字后面可以跟一个下划线字符。这意味着标签等于指定的十六进制数字,但没有最低有效位。例如,有一个方案:
vm_stk_int#0201_ value:int257 = VmStackValue;
实际上,标签并不等于 0x0201
。为了计算它,我们需要从 0x0201
的二进制表示中删除 LSb:
0000001000000001 -> 000000100000000
因此,标签等于 15 位二进制数 0b000000100000000
。
字段定义
构造函数及其可选标签后面跟着字段定义。每个字段定义的形式为 ident:type-expr
,其中 ident 是字段名称的标识符(匿名字段用下划线替代),type-expr 是字段的类型。这里提供的类型是类型表达式,可能包括简单类型、带有适当参数的参数化类型或复杂表达式。
1023
位和 4
引用)
简单类型
_ a:# = Type;
- 这里Type.a
是 32 位整数_ a:(## 64) = Type;
- 这里Type.a
是 64 位整数_ a:Owner = NFT;
- 这里NFT.a
是Owner
类型_ a:^Owner = NFT;
- 这里NFT.a
是指向Owner
类型的cell引用,意味着Owner
存储在下一个cell引用中。
匿名字段
_ _:# = A;
- 第一个字段是匿名的 32 位整数