//pack 二进制打包 namespace raw.pack; var typeCodes = { ['x'#] = "byte"; ['c'#] = "byte"; ['b'#] = "byte"; ['B'#] = "BYTE"; ['?'#] = "byte"; ['h'#] = "word"; ['e'#] = "WORD"; ['H'#] = "WORD"; ['i'#] = "int"; ['I'#] = "INT"; ['l'#] = "int"; ['L'#] = "INT"; ['n'#] = "int"; ['N'#] = "INT"; ['q'#] = "long"; ['Q'#] = "LONG"; ['f'#] = "float"; ['d'#] = "double"; ['s'#] = "byte"; ['p'#] = "byte"; ['P'#] = "pointer"; } var typeSize = { word = 2; WORD = 2; int = 4; INT = 4; long = 8; LONG = 8; float = 4; double = 8; pointer = 4; } reverse = function(buf, i, size) { var e = i + size; while (i < e) { e--; buf[i],buf[e] = buf[e],buf[i]; i++; } }; var byteOrderChars = { ['<'#]=1,['>'#]=1,['!'#]=1,['='#]=1,['@'#]=1 } var _parseFormat = function(fmt,enableArray) { var fields = {}; var fieldIndex = 1; var i; var byteOrder = fmt[1]; if(!byteOrderChars[byteOrder]){// 检查字节序标记 byteOrder = '@'#; // 默认小端序 i=1; } else{ i=2; } while(i <= #fmt) { var ch = fmt[i]; // 解析重复次数 var count = ""; if(ch <= '9'# && ch >= '0'#){ count = ..string.match(fmt,"\d+",i); i = i+#count; ch = fmt[i]; } count = #count ? tonumber(count) : 1; if(i > #fmt) break; var aardioType = typeCodes[ch]; if(!aardioType) { return null,"不支持的格式字符: " + fmt[[i]]; } // 特殊处理字符串类型 if(ch == 's'#) { ..table.push(fields, { type = "byte"; count = count; isString = true; name = "field" ++ fieldIndex; }); fieldIndex++; } elseif(ch == 'x'#) { // pad byte,跳过 ..table.push(fields, { type = "byte"; count = count; isPad = true; name = "pad" ++ fieldIndex; }); fieldIndex++; } else if(count > 1 && enableArray ){ ..table.push(fields, { type = aardioType; count = count; name = "field" ++ fieldIndex; }); fieldIndex++; } else{ for(j=1; count; 1) { ..table.push(fields, { type = aardioType; count = 1; name = "field" ++ fieldIndex; //char = ch; }); fieldIndex++; } } i++; } return fields, byteOrder; } pack = function(fmt,...){ return into(fmt,null,0,...) } into = function(fmt,outBuffer,offset,...) { var values = type(...)=="table"?...:{...}; var valueIndex = 1; var fields, byteOrder = _parseFormat(fmt); if(!fields) return null,byteOrder; var structDef = { @{_readonly=false}}; if(byteOrder!='@'#){ structDef._struct_aligned = 1; } var bigEndian = byteOrder=='>'# || byteOrder=='!'#; for(i=1; #fields; 1) { var field = fields[i]; if(field.isPad) { // pad byte,填充 0 if(field.count == 1) { structDef[field.name] = 0; structDef._struct = (structDef._struct || "") ++ "byte " ++ field.name ++ ";"; } else { structDef[field.name] = ..raw.buffer(field.count, 0); structDef._struct = (structDef._struct || "") ++ "byte " ++ field.name ++ "[" ++ field.count ++ "];"; } } elseif(field.isString) { var value = values[valueIndex] || ""; if(type(value) == "string") { // 确保字符串长度不超过指定长度 if(#value > field.count) { value = ..string.slice(value, 1, field.count); } elseif(#value < field.count) { // 用 0 填充到指定长度 value = value ++ ..string.repeat('\0', field.count - #value); } } structDef[field.name] = value; structDef._struct = (structDef._struct || "") ++ "byte " ++ field.name ++ "[" ++ field.count ++ "];"; valueIndex++; } else { var value = values[valueIndex] || 0; structDef[field.name] = value; structDef._struct = (structDef._struct || "") ++ field.type ++ " " ++ field.name ++ ";"; valueIndex++; } } if(!outBuffer){ outBuffer = ..raw.buffer(structDef); } else{ if(type(outBuffer)==="table"){ if(..string.builder ? ..string.builder.is(outBuffer) ){ if(offset===null) offset = outBuffer.size()+1 else offset++; outBuffer.writeAt(offset,structDef) } else{ error("写入目标只能指定为缓冲区或 string.builder 对象",2); } } else{ if(!offset) offset = 0; ..raw.convert(structDef,outBuffer,offset); } } if(bigEndian){ var structDef = { @{_readonly=false}}; if(byteOrder!='@'#){ structDef._struct_aligned = 1; } for(i=1; #fields; 1) { var field = fields[i]; if(field.isPad) { // pad byte,填充 0 if(field.count == 1) { structDef[field.name] = 0; structDef._struct = (structDef._struct || "") ++ "byte " ++ field.name ++ ";"; } else { structDef[field.name] = ..raw.buffer(field.count, 0); structDef._struct = (structDef._struct || "") ++ "byte " ++ field.name ++ "[" ++ field.count ++ "];"; } } elseif(field.isString) { var value = values[valueIndex] || ""; if(type(value) == "string") { // 确保字符串长度不超过指定长度 if(#value > field.count) { value = ..string.slice(value, 1, field.count); } elseif(#value < field.count) { // 用 0 填充到指定长度 value = value ++ ..string.repeat('\0', field.count - #value); } } structDef[field.name] = value; structDef._struct = (structDef._struct || "") ++ "byte " ++ field.name ++ "[" ++ field.count ++ "];"; valueIndex++; } else { var value = values[valueIndex] || 0; structDef[field.name] = value; var size = typeSize[field.type] if(size>1){ reverse(outBuffer,..raw.sizeof(structDef)+1+offset,size); } structDef._struct = (structDef._struct || "") ++ field.type ++ " " ++ field.name ++ ";"; valueIndex++; } } } return outBuffer; } var _convert = function(fmt, data,offset,supportArrayFields) { data = ..raw.buffer(data); var fields, byteOrder = _parseFormat(fmt,supportArrayFields); if(!fields) return null,byteOrder; var structDef = { @{_readonly=false}}; if(byteOrder!='@'#){ //只有 "@" 格式与 C/C++ 原生结构体的对齐方式兼容 structDef._struct_aligned = 1; } if(!offset)offset=0; var bigEndian = byteOrder=='>'# || byteOrder=='!'#; for(i=1; #fields; 1) { var field = fields[i]; if(field.isPad) { // pad byte if(field.count == 1) { structDef._struct = (structDef._struct || "") ++ "byte " ++ field.name ++ ";"; } else { structDef._struct = (structDef._struct || "") ++ "byte " ++ field.name ++ "[" ++ field.count ++ "];"; } } elseif(field.isString) { structDef._struct = (structDef._struct || "") ++ "byte " ++ field.name ++ "[" ++ field.count ++ "];"; } else { if(bigEndian){ var size = typeSize[field.type] if(size>1){ var off = ..raw.sizeof(structDef)+1+offset; for(i=1;field.count;1){ reverse(data,off,size); off=off+size; } } } if(field.count>1){ if(field.type == "WORD"){ structDef[field.name] = [] //WORD [] 不指定值时默认视为字符串,并执行 UTF-16 到 UTF-8 的转换。 } structDef._struct = (structDef._struct || "") ++ field.type + " " + field.name ++ "[" ++ field.count ++ "];"; } else{ if(field.type == "LONG"){ structDef[field.name] = ..math.size64() } structDef._struct = (structDef._struct || "") ++ field.type ++ " " ++ field.name ++ ";"; } } } return ..raw.convert(data, structDef,offset),fields; } convert = function(fmt,data,offset) { if(!data) error("参数 2 必须指定字符串或 buffer",2); var result,fields = _convert(fmt,data,offset,true); if(!result) return null,fields; return ..table.assign({}, result);//移除元表 } //@Undocumented unpackArray2 = function(fmt,data,offset,supportArrayFields){ if(!data) error("参数 2 必须指定字符串或 buffer",2); if(supportArrayFields===null) supportArrayFields = true; var result,fields = _convert(fmt,data,offset,supportArrayFields); if(!result) return null,fields; var values = []; for(i=1; #fields; 1) { var field = fields[i]; if(!field.isPad) { var value = result[field.name]; if(field.isString) { // 字符串类型,移除尾部的0 if(type(value) == "string") { var nullPos = ..string.find(value, '\0'); if(nullPos) { value = ..string.slice(value, 1, nullPos - 1); } } } ..table.push(values, value); } } return values,..raw.sizeof(result); } unpackArray = function(fmt,data,offset,supportArrayFields){ if(!data) error("参数 2 必须指定字符串或 buffer",2); var result,fields = unpackArray2(fmt,data,offset,supportArrayFields) if(result) return result; return null,fields } unpack = function(fmt,data,offset){ if(!data) error("参数 2 必须指定字符串或 buffer",2); var values,err = unpackArray(fmt,data,offset,false); if(values) return ..table.unpack(values); } getStruct = function(fmt) { var fields, byteOrder = _parseFormat(fmt,true); if(!fields) return null,byteOrder; var structDef = { @{_readonly=false}}; if(byteOrder!='@'#){ //只有 "@" 格式与 C/C++ 原生结构体的对齐方式兼容 structDef._struct_aligned = 1; } var bigEndian = byteOrder=='>'# || byteOrder=='!'#; // 构建结构体定义用于解包 for(i=1; #fields; 1) { var field = fields[i]; if(field.isPad) { // pad byte if(field.count == 1) { structDef._struct = (structDef._struct || "") ++ "byte " ++ field.name ++ ";"; } else { structDef._struct = (structDef._struct || "") ++ "byte " ++ field.name ++ "[" ++ field.count ++ "];"; } } elseif(field.isString) { // 字符串类型 structDef._struct = (structDef._struct || "") ++ "byte " ++ field.name ++ "[" ++ field.count ++ "];"; } else { if(field.count>1){ if(field.type == "WORD"){ structDef[field.name] = [] //WORD [] 不指定值时默认视为字符串,并执行 UTF-16 到 UTF-8 的转换。 } structDef._struct = (structDef._struct || "") ++ field.type + " " + field.name ++ "[" ++ field.count ++ "];"; } else{ structDef._struct = (structDef._struct || "") ++ field.type ++ " " ++ field.name ++ ";"; if(field.type == "LONG"){ structDef[field.name] = ..math.size64(); } } } } if(structDef._struct_aligned){ var size1 = ..raw.sizeof(structDef); structDef._struct_aligned = null; if(..raw.sizeof(structDef)!=size1){ structDef._struct_aligned = 1; } } return structDef; } getCode = function(fmt){ var struct,err = getStruct(fmt); if(!struct) return null,err; var code = '{\n\t' + ..string.replace(struct._struct,";",';\n\t'); code = ..string.replace(code,"(!\wWORD \a\w+%\[\])","\1=[]"); code = ..string.replace(code,"(!\wLONG \a\w+);","\1=..math.size64();"); if(struct._struct_aligned){ code = code + "_struct_aligned=1" } code = ..string.trimright(code) + '\n}'; return code; } each = function(fmt,buffer,offset=0){ if(!buffer) error("参数 2 必须指定字符串或 buffer",2); var c = 0; var total,size,array = #buffer,1; return function(){ if(offset+size > total ) return; array,size = unpackArray2(fmt,buffer,offset,true) offset = offset + size; c++; return c,array; } } sizeof = function(fmt) { var struct= getStruct(fmt); return struct ? ..raw.sizeof(struct) : 0; } calcsize = sizeof; self@ = { _call = pack; } //@guide [范例代码](doc://example/aardio/Raw/raw.pack.html) /*****intellisense() raw.pack = 用于二进制打包的库。\n兼容 Python struct.pack 格式化字符。\nraw.pack 与 raw.pack.unpack 会自动处理字节序。\n仅 `>` 与 `!` 开始的大端序与 aardio 的默认字节序不同需要转换。\n\n导入此库与 string.builder 库可使用 string.builder 的 pack 函数连续或批量打包。\n\n建议应 raw.pack.getCode 获取与打包解包格式化字符串一致的结构体声明,\n然后直接使用 raw.convert 解包,使用 raw.buffer 或 raw.tostring 打包。 raw.pack(.(format,->->->) = 参数 @format 指定格式化字符串。\n其他不定个数的参数指定待打包的值,也可以传入包含多个参数的单个数组参数。\n成功返回字节串( buffer 类型 ),\n失败返回 null,错误信息。\n\n为了与 Python 的用法保持一致,\n每个打包解包的参数都必须是单个非数组值(数值、字符串、buffer 等)。\n但改用 getStruct 或 getCode 可以得到支持数组的 aardio 结构体声明。\n将结构体作为参数就以用 raw.convert 解包或调用 raw.buffer 或 raw.tostring 打包。 raw.pack.into(.(format,buffer,offset,->->->) = 参数 @format 指定格式化字符串。\nbuffer 指定缓冲区( buffer 类型字节串 ) 对象,传 null 值则创建新的 buffer。\noffset 指定写入位置的字节偏移量,默认为 0。\n其他不定个数的参数指定待打包的值,也可以传入包含多个参数的单个数组参数。\n成功返回字节串( buffer 类型 ),\n失败返回 null,错误信息。\n\n为了与 Python 的用法保持一致,\n每个打包解包的参数都必须是单个非数组值(数值、字符串、buffer 等)。\n但改用 getStruct 或 getCode 可以得到支持数组的 aardio 结构体声明。\n将结构体作为参数就以用 raw.convert 解包或调用 raw.buffer 或 raw.tostring 打包。 raw.pack.convert(.(format,buffer,offset) = 参数 @format 指定格式化字符串。\n参数 @buffer 指定需要解包的 buffer 或者字符串。\n可选用 @offset 参数指定偏移字节数,默认为 0。\n成功返回解包后的结构体对象(指定重复次数的格式字符转为数组) ,失败返回 null,错误信息。 raw.pack.unpackArray(.(format,buffer,offset) = 参数 @format 指定格式化字符串。\n兼容 Python struct.unpack 格式化字符。\n参数 @buffer 指定需要解包的 buffer 或者字符串。\n可选用 @offset 参数指定偏移字节数,默认为 0。\n\n此函数默认将指定重复次数的格式字符解包为单个数组值,\n如果参数 @4 为 false 则展开数组为多个值\n\n成功返回一个包含所有解包值的数组,失败无返回值。 raw.pack.unpack(.(format,buffer,offset) = 参数 @format 指定格式化字符串。\n兼容 Python struct.unpack 格式化字符。\n参数 @buffer 指定需要解包的 buffer 或者字符串。\n可选用 @offset 参数指定偏移字节数,默认为 0。\n顺序返回解包成功的一个或多个值(指定重复次数的格式字符返回多个值而非数组)。 raw.pack.getStruct(.(format) = 根据参数 @format 指定的格式化字符串生成结构体对象,支持数组字段。\n无对齐时 _struct_aligned 字段值为 1,否则遵守默认的结构体对齐规则。\n无对齐(紧凑结构)时如果巧合了自动对齐规则,则不会添加 _struct_aligned 字段。\n成功返回结构体,失败返回 null,错误信息。 raw.pack.getCode(.(format) = 根据参数 @format 指定的格式化字符串生成创建结构体的 aardio 代码,支持数组字段。\n失败返回 null,错误信息。\n\n此函数调用 raw.pack.getStruct 获取结构体并转换为代码,\n细节请参考该函数说明。 raw.pack.sizeof(.(format) = 根据参数 @format 指定的格式化字符串计算打包后的字节长度。\n失败返回 0 raw.pack.calcsize(.(format) = raw.pack.sizeof 函数的别名。\n根据参数 @format 指定的格式化字符串计算打包后的字节长度。\n失败返回 0 raw.pack.reverse(.(buffer,index,size) = 原地转换字节序。\n参数 buffer 指定字节串( buffer 类型 )。\n此函数不会检查参数 @buffer 的类型,调用者有责任传入正确的类型。\n参数 @index 指定开始转换的索引,起始索引为 1。\n参数 @size 指定要转换的字节长度。 raw.pack.each(format,buffer,offset=0) = @for count,items in raw.pack.each("<10s2HI",buffer){\n __/*\n解包迭代器,每次都返回包含所有字段的数组。\n不展开数组字段( 例如 "2H" 格式化为 WORD[] 数组 )\n\n相当于循环调用 raw.pack.unpackArray 函数。\n第一次循环只要 buffer 的长度大于 1 就会执行。\n后续会安全地检测剩下的长度是否足够解包,长度不够时退出循环。\n\n参数 @format 指定格式化字符串。\n兼容 Python struct.unpack 格式化字符。\n参数 @buffer 指定需要解包的 buffer 或者字符串。\n可选用 @offset 参数指定偏移字节数,默认为 0。 \n*/\n} end intellisense*****/ /**details(使用必读) **介绍:** ardio 的 raw.pack 是 Python 的 struct 库的兼容实现。 使用的二进制格式化语法与 Python 相同( [请参考 Python struct 模块文档](https://docs.python.org/zh-cn/3.8/library/struct.html) )。 **工具:** 使用「aardio 工具 » 转换工具 » 原生类型转换」 可以将 Python 二进制格式化串自动转换为结构体声明,并生成用结构体打包解包的示例。 使用结构体打包解包仅需要用到内置函数 raw.convert 与 raw.buffer, 不需要引入 raw.pack 库。 aardio 使用结构体处理二进制更为简单,速度也更快,结构体描述二进制数据的可读性也更好。 但是对于大端序格式串使用 raw.pack 会省事一些,这是因为 raw.pack 与 raw.pack.unpack 会自动转换大端序。 但结构体使用的是小端序,大端序要使用 raw.swap 或 math.size64 的 swap 函数手动转换。 **函数对比:** 有关的 Python 函数与 aardio 函数的对应关系如下。 | Python 函数 | aardio 函数 | | --- | --- | | struct.pack | raw.pack | | struct.pack_into | raw.pack.into | | struct.unpack | raw.pack.unpack | | struct.unpack_from 返回多值 | raw.convert 返回单个结构体 | | struct.calcsize | raw.sizeof 或 raw.calcsize | | struct.iter_unpack |raw.pack.each | raw.pack, raw.pack.unpack 与 Python 相同处理的都是单个非数组值, 而 raw.pack.unpackArray,raw.pack.getStruct,raw.pack.getCode 都将指定重复次数的格式化字符处理为数组字段。 例如 raw.pack.getCode("@10H") 会返回包含结构体声明的 aardio 代码 `{ WORD field1[10]; }` 。 raw.pack.getCode 将 Python 的二进制格式化串转换为 aardio 结构体声明, 「aardio 工具 » 转换工具 » 原生类型转换」就是调用 raw.pack.getCode 转换二进制格式化串。 注意 raw.pack 返回的是 buffer 而不是字符串,在 aardio 里大部份函数通常可以用 buffer 替代字符串参数。 可以用 tostring(buffer) 将 buffer 转换为字符串。 **格式化字符:** 完整的格式化字符请查看 Python 文档,下面仅说明 aardio 中需要注意的一些格式。 - `s` 格式打包时可输入字符串或 buffer,解包时返回字符串, aardio 中字符串也是二进制兼容的,都可以作为字节数组使用。 `s` 格式打包时,不需要预先处理字符串的长度, raw.pack 会自动截断超出长度的部分,不足的部分会自动填充为 0。 这是因为 raw.pack 底层基于 aardio 的结构体,而结构体自动就会处理这些问题。 `s` 格式对应 Python 中的字节串(bytes)。 在 aardio 与 Python 交互时,aardio 也会将 Python 字节串转换为 aardio 中的 buffer 。 > aardio 中的 buffer 是可读写的,而 Python 字节串是只读的。 可以将 Python 格式串先转换为 aardio 结构体, 在结构体中 byte [] 类型字段默认处理为字符串值。 如果值预先指定为 buffer 类型则解包时也会填充数据到原来的 buffer 中。 如果值预先指定数组,则解包时会生成一个新的数组。 在 Python 中 `s` 格式打包时需经预先处理长度,翻译为 aardio 代码就是这样写: ```aardio import raw.pack; var variableLengthString = "abcd" var size = 5; var fixedSizeString; //使用定长字符串 if(#variableLengthString < size ) { fixedSizeString = variableLengthString + string.repeat(size-#variableLengthString) } else{ fixedSizeString = string.left(variableLengthString,size) } var buf = raw.pack("<5sb",variableLengthString,3); ``` 上面的代码利用 buffer 的定长特性可以简化如下: ```aardio var fixedSizeBuffer = raw.buffer(size,variableLengthString) buf = raw.pack("<5sb",fixedSizeBuffer,3) //参数里字符串与 buffer 可以互换 ``` 但其实 raw.pack 本就可以将字符串或 buffer 自动转换为定长数据, 所以可以直接写: ```aardio buf = raw.pack("<5sb",variableLengthString/*太长太短都会自动处理*/,3) ``` raw.pack 底层是基于 aardio 的结构体打包数据, aardio 结构体里的数组字段都不要求传入定长数组,太长太短 aardio 都会自动处理。 所以处理这类问题,用 aardio 可以少写很多代码。 - `p` 格式在 aardio 中作为 `s` 格式处理。 `p` 格式的唯一区别是第一个字节记录了长度,aardio 不会移除这个长度标记,也不会清除尾部的填充字节。 这种格式现在基本不太可能用到,可以忽略。 - `Q` 格式表示无符号 64 位整数,在 aardio 中解包时返回的是 math.size64 对象。 aardio 使用 math.size64 对象存储 64 位无符号整数,使用 tonumber 可以将其转换为普通的 64 位浮点数。 - `?` 格式在 aardio 中处理为 `byte` 类型,这个类型原本表示单字节 bool 类型, 但是 aardio 原生类型的 bool 类型是 4 字节,所以 `?` 以单字节 `byte` 替代。 aardio 中以 0 为 false,非 0 数值为 true,所以在使用上并没有太大区别。可以用双冒号 `!!` 将数值转换为 aardio 中的 boolean 类型。 end details**/