说明: 《DTK Guide》的完整内容,请查附件pdf(带目录)。
基础篇
1 DTK语言
1.1 数据类型
在DTK里,基本类型的数据不需要专门的类型定义,其命名规则即隐含了类型定义,如: cRet 表示char型数据,名字为Ret。即:小写的前缀表示类型,以大写字母开头的是名称,二者构成了完整的类型命名。
DTK的前缀表
前缀 | SIZE(bytes) | 说明 | 举例 |
c | 1 | int8_t | cRet |
uc | 1 | uint8_t | ucType |
s | 1 | int8_t | sBuf[10] |
sh | 2 | int16_t | shId |
w | 2 | uint16_t | wSeq |
i | 4 | int32_t | iCode |
dw | 4 | uint32_t | dwTime |
ll | 8 | int64_t | llAddr |
qw | 8 | uint64_t | qwKey |
不满足的“基本类型”命名规则的数据类型,都是复杂类型,必须有明确的类型定义。
其描述格式如下:
类型名称 = 类型定义
其中,类型定义必须同在1行,可以通过“\”行连接符来连接多行定义。
举例: head = wVersion + cCmd + wLen 表示类型head有3个数据成员wVersion、cCmd和wLen。
根据定义的形式,类型定义可以分为:“静态定义”(不含条件逻辑)、“动态定义”(含条件逻辑)、“别名定义”(类型的别名)。
1.2 类型定义
1.2.1 静态定义
静态定义可以是:
1) 另一个类型名称,如:head = cCmd 表示head类型里只有1个成员cCmd。(cCmd是head的子节点)
静态定义也可以包含其他类型定义的组合:
1) 简单组合
即用”+” 把不同的类型定义组合起来,如:head = wVersion + cCmd
2) 按数目重复组合(repeated by count),如:head = wLen + sBuf [ wLen ]其中,“[]”里面的数目可以是DTK表达式的值(参见1.6)
3) 按长度重复组合 (repeated by length) ,如: head = wLen + item { wLen } 其中, item{wLen} 表示 wLen 个字节里全部都是 item 类型的数据。具体有多少个,要根据 item 的定义确定。 其中,“ {} ”里面的长度可以是 DTK 表达式的值(参见 1.6 )
事实上,以上的组合形式可以任意混合使用,比如: head = cCnt + ( wLen + ( ucCmd + wVersion { wLen } ) [ cCnt ]其中,通过“()”将数据元素封装成一个单元。
1.2.2 别名定义
别名定义类似于C语言中的typedef。其完×××式如下: 类型名称1 == 类型名称2表示“类型1”拥有“类型2”完全一样的定义,只是名字不同。举例: msg_0x1 = cCmd + wVer
msg_0x2 == msg_0x1表示msg_0x2拥有和msg_0x1完全一样的格式1.2.3 动态定义
动态定义是包含条件逻辑的定义形式:类型名称 = { 条件语句}
DTK 目前支持“ if/elif/else ”、“ switch ”语句,以及 C 语言形式的“表达式”(参见 1.6 )
“if/elif/else”式定义举例:类型名称 = { if 表达式1 : 类型定义1 elif 表达式2 : 类型定义2 else : 类型定义3 }
“switch” 式定义举例: 类型名称 = { switch 表达式 取值 1 : 类型定义 1 取值 2 : 类型定义 2 * : 类型定义 3 }
其中“*”相当于default分支。也可以指定取值的连续范围或取值列表,如:1~5 : 定义4 # 使用 “~”表示连续范围(左右数值都包含)1,3,6 : 定义5 # 使用 “,”分隔数值列表的不同数值
其中,以上类型定义可以是任意类型的“类型定义”(无论是静态定义、动态定义还是别名定义)。
举例:
======samples/basic/pay.def# 支付信息,包括用户ID、支付方式、以及支付的具体信息payInfo = dwUserId + cType + info# 支付的具体信息info = { if dwUserId < 50000000 : { # VIP会员,有多种支付方式 switch cType 1 : =virtualMoney # 虚拟货币,这里以“别名”的形式使用virtualMoney的定义 2 : wTicket # 优惠券 * : dwMoney # 实际货币 } else : dwMoney # 普通会员,只支持“实际货币”购买}
virtualMoney = dwAmount + dwValidTime # 数目、有效时间
1.3 文件包含
DTK的类型定义可以通过@include宏进行文件级复用。其形式如下:@include “filePath”举例:@include “../lib/comm.def”
1.4 如何引用类型
类型的全名形式:文件名.类型名,如:fileA.def中定义了 head 类型,则其全名是:fileA.head
当要引用某类型时,可以: 1) 使用类型的全名,这样可以唯一确定类型。 2) 使用类型的本名(类型名), DTK 会自动推测和提示。
举例:
===samples/basic/product.defproduct = dwId + wType + info
=== samples/basic/pay.def payInfo = dwUserId + cType + info
=== samples/basic/order.def @include "product.def" @include "pay.def"
order = wCount + product[ wCount ] + detail detail == pay.payInfo # 这里“ pay. ”前缀实际可用省略, DTK 会自动推测是 pay.def 里的 payInfo
更多: DTK 如何自动推测类型? 如何在同一文件里定义同名类型? 如果 include 的不同文件里有同名类型,是否冲突? 如果 include 的文件路径不同,但文件名相同,造成名字冲突,如何解决? 以及在这些情况下“如何引用类型”,参见“中级篇”第 1.1 节“类型的名字空间”
1.5 数据对象
数据对象即数据以某种数据类型的形式解析之后的结果。数据对象的“全名”,并不等同于其类型名称,而是要根据它在“根对象”的层次位置决定。
举例:head = wCmd + dwVer req = head + dwAddr resp = head + dwPhone
当数据以 req 的形式解析,得到以下数据对象(下面是全名): req # 根对象(只有根对象的全名和其类型名称一样) req.head req.head.wCmd req.head.dwVer req.dwAddr
当数据以 resp 的形式解析,得到以下数据对象(下面是全名): resp # 根对象 resp.head resp.head.wCmd resp.head.dwVer resp.dwPhone
数据对象的“全名”是明确标识某对象的唯一方式。
1.6 表达式
DTK的表达式支持C语言形式的绝大部门的常用算术表达式、逻辑表达式及其组合(除了”++”和”--“ )。参与运算的运算数可以是:数字、数据对象、DTK的内建函数(参见附录)。
DTK的表达式用于:1) 指定重复元素的数目和最大长度,参见本篇1.2.12) 条件语句的条件判断,参见本篇1.2.3
举例:req = head + (dwSN + order){ head.wLen - sizeof(head) } head = wVersion + cCmd + wLen
以及本篇 1.2.3 中例子
1.7 如何引用对象
1) 可以使用数据对象的“全名”(参见1.5)明确指明2) 也可以简化,使用数据对象的本名或部分全名,DTK会自动进行推测。(推荐)
以本篇1.6的例子来说,head.wLen 和 head都是简化的引用形式。若以全名引用,则是:
req = head + (dwSN + order){ req.head.wLen - sizeof(req.head) }
如果 DTK 的推测结果并不符合要求,请使用“全名”来引用对象。
更多:
DTK如何根据自动推测具体对象?参见中级篇1.1.4
1.8 其他语言特性
1.8.1 注释
DTK使用” # ” 来表示注释开始。和shell的注释风格相同。
1.8.2 宏
DTK目前支持的一些宏命令,以@开始,如@include,具体参见附录。
1.8.3 内建函数
DTK目前支持的内建函数参见附录。
1.8.4 __testcases
针对动态定义(即有多个分支),如何进行遍历的语法检查,或生成不同条件下的解码样本, 可以使用__testcases。参见“中级篇”第4节。
1.8.5 event
DTK允许在某数据对象解码前和解码后进行自定义的python脚本处理,即可以定义__begin和__end事件处理逻辑,参见“高级篇”第1节。