使用Perl6解析二进制结构的最佳选择是什么。
在Perl5中,我们在Perl6上有打包/解包方法它们似乎是实验性的是否可以使用Perl6语法来解析二进制数据,假设我有一个文件,其中包含以下二进制格式的记录:
struct record {
short int ut_type;
char ut_line[UT_LINESIZE];
char ut_id[4];
char ut_user[UT_NAMESIZE];
char ut_host[UT_HOSTSIZE];
}
是否可以使用Perl6语法解析此文件?
答案 0 :(得分:6)
使用Perl6解析二进制结构的最佳选择是什么?
特别是考虑到你了解P5的打包/解包,新的P5pack模块似乎是合适的解决方案。 (我没有测试过它。它是新的.Aiui它没有实现所有的东西而且它不会模仿P5的包装盲目地。但它是Liz。)
如果上面链接的P5包接口的新纯P6实现没有做你需要做的事情,另一个明显的解决方案是在你的P6代码中使用原始的P5函数,由常规的perl 5二进制文件执行。以下是不完整/未经测试但我的意思大致如下:
use Inline::Perl5 ; my \P5 = Inline::Perl5.new ;
my $mem = Buf ... ;
my $hex = P5.call('unpack', 'H*', $mem) ;
(或者,相反,将主线写为P5代码,通过Inline::Perl6
添加P6代码。)
在当前版本的P6中,6.c
,语法只能处理文本。
2年前P6er“skids”写道:
"There are enough people wanting binary grammars that I think it will come to be"(写于2016年)。
那时他们还整理了以下相关链接:
答案 1 :(得分:4)
我完全赞同raiph的回答和评论,只是想补充一点。
我认为有两种类型的解析,一种是根据内部发现的内容进行解析,另一种是使用描述数据排列方式的外部模式。二进制数据可以以任何方式排列msgpack是前者对二进制数据的一个例子。
您正在使用的示例,解压缩二进制结构是后者的一个示例。 看起来NativeCall CStruct 几乎直接做你想要的事情。也许它已经可以做到并且我只是不知道,但似乎缺乏表达嵌入式大小数组的能力。 (这有效吗?)
如果没有这个,你的第一份工作就是弄清楚你要解压缩的结构。有几种方法可以做到这一点。第一个是最容易的,但也许最容易出错 - 只需看一下结构。 (我将编写一些假定义,以便我可以使用实数):
record.h:
#define UT_LINESIZE 80
#define UT_IDSIZE 4
#define UT_NAMESIZE 50
#define UT_HOSTSIZE 20
struct record {
short int ut_type;
char ut_line[UT_LINESIZE];
char ut_id[UT_IDSIZE];
char ut_user[UT_NAMESIZE];
char ut_host[UT_HOSTSIZE];
};
考虑到这一点,我可以捕获每个字段的偏移量和大小:
constant \type-size := nativesizeof(int16);
constant \line-size := 80;
constant \id-size := 4;
constant \user-size := 50;
constant \host-size := 20;
constant \record-size := type-size + line-size + id-size + user-size + host-size;
constant \type-offset := 0;
constant \line-offset := type-offset + type-size;
constant \id-offset := line-offset + line-size;
constant \user-offset := id-offset + id-size;
constant \host-offset := user-offset + id-size;
此处有一些注意事项 - 您必须了解您的格式,以便考虑任何alignment or padding。这里的例子比其他人更容易。
这为我们提供了足够的信息来确定二进制结构中哪些字节映射到每个字段。
接下来,您需要将每个字节块转换为正确的Perl类型。 NativeCall的nativecast例程可以为您做到这一点。它可以轻松地将一大块字节转换为许多Perl数据类型。
我将假设您的字段是C字符串,始终由NUL正确终止,并且适合解码为UTF8。您可以针对其他特定情况进行调整。
use NativeCall;
class record {
has Int $.ut-type;
has Str $.ut-line;
has Str $.ut-id;
has Str $.ut-user;
has Str $.ut-host;
}
sub unpack-buf(Mu:U $type, Blob $buf, $offset, $size) {
nativecast($type, CArray[uint8].new($buf[$offset ..^ $offset+$size]))
}
sub unpack-record($buf) {
record.new(
ut-type => unpack-buf(int16, $buf, type-offset, type-size),
ut-line => unpack-buf(Str, $buf, line-offset, line-size),
ut-id => unpack-buf(Str, $buf, id-offset, id-size),
ut-user => unpack-buf(Str, $buf, user-offset, user-size),
ut-host => unpack-buf(Str, $buf, host-offset, host-size)
)
}
然后,您可以从数据文件中搜索/读取二进制数据以获取单个记录,或者只是遍历所有记录:
my @data = gather {
given 'data'.IO.open(:bin) {
while .read(record-size) -> $buf {
take unpack-record($buf)
}
.close
}
}
因为我们已经从C结构定义手动复制了东西,所以必须更新对它的任何更改,并且对齐/填充可能总是让我们感到厌烦。
另一种选择是直接读取C头文件并使用sizeof()
和offsetof()
向我们提供所有数字。这自然会考虑对齐/填充。您甚至可以在Perl代码中使用TCC来直接访问头文件。您可以使用此代码将所有内容从C constant
文件中拉出来,而不是上面的所有.h
行。
my $tcc = TCC.new('');
$tcc.compile: q:to/END/;
#include <stddef.h>
#include "record.h"
size_t record_size() { return sizeof(struct record); }
size_t type_offset() { return offsetof(struct record, ut_type); }
size_t type_size() { return sizeof(short int); }
size_t line_offset() { return offsetof(struct record, ut_line); }
size_t line_size() { return UT_LINESIZE; }
size_t id_offset() { return offsetof(struct record, ut_id); }
size_t id_size() { return UT_IDSIZE; }
size_t user_offset() { return offsetof(struct record, ut_user); }
size_t user_size() { return UT_NAMESIZE; }
size_t host_offset() { return offsetof(struct record, ut_host); }
size_t host_size() { return UT_HOSTSIZE; }
END
$tcc.relocate;
my &record-size := $tcc.bind('record_size', :(--> size_t));
my &type-offset := $tcc.bind('type_offset', :(--> size_t));
my &type-size := $tcc.bind('type_size', :(--> size_t));
my &line-offset := $tcc.bind('line_offset', :(--> size_t));
my &line-size := $tcc.bind('line_size', :(--> size_t));
my &id-offset := $tcc.bind('id_offset', :(--> size_t));
my &id-size := $tcc.bind('id_size', :(--> size_t));
my &user-offset := $tcc.bind('user_offset', :(--> size_t));
my &user-size := $tcc.bind('user_size', :(--> size_t));
my &host-offset := $tcc.bind('host_offset', :(--> size_t));
my &host-size := $tcc.bind('host_size', :(--> size_t));