【问题标题】:Parsing binary structure with Perl6 Grammar用 Perl6 语法解析二进制结构
【发布时间】:2018-01-11 07:49:57
【问题描述】:

使用 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 语法解析这个文件吗?

【问题讨论】:

    标签: raku


    【解决方案1】:

    用 Perl6 解析二进制结构的最佳选择是什么?

    特别是考虑到您了解 P5 的打包/解包,新的P5pack 模块似乎是合适的解决方案。 (我还没有测试过它。它是新的。Aiui 它没有实现所有东西,也没有模仿 P5 的包盲目。但它是 Liz。)


    如果上面链接的 P5 包接口的新纯 P6 实现不能满足您的需要,另一个明显的解决方案是在您的 P6 代码中使用由常规 perl 5 二进制文件执行的原始 P5 函数。以下内容不完整/未经测试,但我的意思大致类似于:

    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 年)。

    当时他们还整理了以下相关链接:

    【讨论】:

      【解决方案2】:

      我完全同意 raiph 的回答和 cmets,只是想补充一点。

      我考虑了两种类型的解析,一种是您可以根据内部找到的内容进行解析,另一种是您使用描述数据排列方式的外部架构。二进制数据可以任意排列msgpack 是二进制数据的前者示例。

      您正在使用的示例,解压二进制结构是后者的示例。 看起来NativeCallCStructs几乎可以直接做你想做的事。也许它已经可以做到,我只是不知道,但它似乎缺乏表达嵌入式大小数组的能力。 (这还有效吗?)

      如果没有这些,您的首要工作就是弄清楚您要解包的结构。有几种方法可以做到这一点。第一个是最简单的,但也许最容易出错——看看结构。 (我将编造一些虚假的定义,以便我可以使用真实的数字):

      记录.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 直接访问头文件。除了上面所有的 constant 行之外,您可以使用它从 C .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));
      

      【讨论】:

        猜你喜欢
        • 2019-10-11
        • 2011-07-15
        • 1970-01-01
        • 1970-01-01
        • 2021-12-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-05-20
        相关资源
        最近更新 更多