如何使用Perl解析C头文件?

时间:2009-06-15 06:33:14

标签: python c perl parsing header-files

我有一个头文件,其中有一个大型结构。我需要使用一些程序读取这个结构,并对结构的每个成员进行一些操作并将它们写回来。

例如我有一些像

这样的结构
const BYTE Some_Idx[] = {
4,7,10,15,17,19,24,29,
31,32,35,45,49,51,52,54,
55,58,60,64,65,66,67,69,
70,72,76,77,81,82,83,85,
88,93,94,95,97,99,102,103,
105,106,113,115,122,124,125,126,
129,131,137,139,140,149,151,152,
153,155,158,159,160,163,165,169,
174,175,181,182,183,189,190,193,
197,201,204,206,208,210,211,212,
213,214,215,217,218,219,220,223,
225,228,230,234,236,237,240,241,
242,247,249};

现在,我需要阅读此内容并对每个成员变量应用一些操作,并创建一个具有不同顺序的新结构,如:

const BYTE Some_Idx_Mod_mul_2[] = {
8,14,20, ...
...
484,494,498};

是否有可用于此的Perl库?如果不是Perl,那么像Python这样的东西也行。

有人可以帮忙!!!

9 个答案:

答案 0 :(得分:9)

保持数据位于标题中会使得使用Perl等其他程序变得更加棘手。您可能考虑的另一种方法是将此数据保存在数据库或其他文件中,并根据需要重新生成头文件,甚至可能作为构建系统的一部分。这样做的原因是生成C比解析C容易得多,编写一个解析文本文件并为您创建标题的脚本是微不足道的,甚至可以从您的构建系统调用这样的脚本。

假设您希望将数据保存在C头文件中,则需要以下两种方法之一来解决此问题:

  • 一个快速的一次性脚本,可以准确地(或接近确切地)解析您描述的输入。
  • 一个通用的,编写良好的脚本,可以解析任意C并且通常可以处理许多不同的头文件。

第一种情况似乎比第二种情况更常见,但很难从你的问题中判断出是否可以通过需要解析任意C或需要解析此特定文件的脚本的脚本来解决这个问题。对于适合您特定情况的代码,以下内容适用于您的输入:

#!/usr/bin/perl -w

use strict;

open FILE, "<header.h" or die $!;
my @file = <FILE>;
close FILE or die $!;

my $in_block = 0;
my $regex = 'Some_Idx\[\]';
my $byte_line = '';
my @byte_entries;
foreach my $line (@file) {
    chomp $line;

    if ( $line =~ /$regex.*\{(.*)/ ) {
        $in_block = 1;
        my @digits = @{ match_digits($1) };
        push @digits, @byte_entries;
        next;
    }

    if ( $in_block ) {
        my @digits = @{ match_digits($line) };
        push @byte_entries, @digits;
    }

    if ( $line =~ /\}/ ) {
        $in_block = 0;
    }
}

print "const BYTE Some_Idx_Mod_mul_2[] = {\n";
print join ",", map { $_ * 2 } @byte_entries;
print "};\n";

sub match_digits {
    my $text = shift;
    my @digits;
    while ( $text =~ /(\d+),*/g ) {
        push @digits, $1;
    }

    return \@digits;
}

解析任意C对于许多应用程序来说有点棘手并且不值得,但也许您需要实际执行此操作。一个技巧是让GCC为你做解析,并使用名为GCC::TranslationUnit的CPAN模块读入GCC的解析树。 这是编译代码的GCC命令,假设您有一个名为test.c的文件:

gcc -fdump-translation-unit -c test.c

这是在解析树中读取的Perl代码:

  use GCC::TranslationUnit;

  # echo '#include <stdio.h>' > stdio.c
  # gcc -fdump-translation-unit -c stdio.c
  $node = GCC::TranslationUnit::Parser->parsefile('stdio.c.tu')->root;

  # list every function/variable name
  while($node) {
    if($node->isa('GCC::Node::function_decl') or
       $node->isa('GCC::Node::var_decl')) {
      printf "%s declared in %s\n",
        $node->name->identifier, $node->source;
    }
  } continue {
    $node = $node->chain;
  }

答案 1 :(得分:6)

很抱歉,如果这是一个愚蠢的问题,但为什么要担心解析文件呢?为什么不编写#includes标头的C程序,根据需要处理它,然后吐出修改后的标头的源代码。我确信这比Perl / Python解决方案更简单,并且它会更可靠,因为标头将由C编译器解析器解析。

答案 2 :(得分:4)

您实际上并未提供有关应如何确定要修改内容的更多信息,而是要解决您的具体示例:

$ perl -pi.bak -we'if ( /const BYTE Some_Idx/ .. /;/ ) { s/Some_Idx/Some_Idx_Mod_mul_2/g; s/(\d+)/$1 * 2/ge; }' header.h

打破这种情况,-p表示循环输入文件,将每行放在$_中,运行提供的代码,然后打印$_。 -i.bak支持就地编辑,使用.bak后缀重命名每个原始文件,并打印到一个名为原始文件的新文件。 -w启用警告。 -e'....'提供要为每个输入行运行的代码。 header.h是唯一的输入文件。

在perl代码中,if ( /const BYTE Some_Idx/ .. /;/ )会检查我们是否在一系列行中,这些行以匹配/const BYTE Some_Idx/的行开头,并以匹配/;/的行结束。 s /.../.../ g尽可能多地进行替换。 /(\d+)/匹配一系列数字。 / e标志表示结果($1 * 2)是应该被评估以生成替换字符串的代码,而不是简单的替换字符串。 $ 1是应该替换的数字。

答案 3 :(得分:3)

如果你需要做的就是修改结构,你可以直接使用正则表达式来拆分并对结构中的每个值应用更改,寻找声明和结束};知道什么时候停止。

如果您真的需要更通用的解决方案,可以使用解析器生成器,例如PyParsing

答案 4 :(得分:2)

有一个名为Parse::RecDescent的Perl模块,它是一个非常强大的递归下降解析器生成器。它附带了一堆例子。其中一个是grammar that can parse C

现在,我不认为这对你的情况很重要,但是使用Parse :: RecDescent的递归下降解析器在算法上比Parse::Yapp或{等工具慢(O(n ^ 2)) {3}}。我没有检查Parse :: EYapp是否带有这样的C-parser示例,但如果是这样,那就是我建议学习的工具。

答案 5 :(得分:2)

Python解决方案(不完整,只是一个提示;))抱歉,如果有任何错误 - 未经过测试

import re
text = open('your file.c').read()
patt = r'(?is)(.*?{)(.*?)(}\s*;)'
m = re.search(patt, text)
g1, g2, g3 = m.group(1), m.group(2), m.group(3)
g2 = [int(i) * 2 for i in g2.split(',')
out = open('your file 2.c', 'w')
out.write(g1, ','.join(g2), g3)
out.close()

答案 6 :(得分:2)

有一个非常有用的名为Convert::Binary::C的Perl模块,它解析C头文件并将结构转换为/向Perl数据结构。

答案 7 :(得分:0)

您可以随时使用pack / unpack来阅读和撰写数据。

#! /usr/bin/env perl
use strict;
use warnings;
use autodie;

my @data;
{
  open( my $file, '<', 'Some_Idx.bin' );

  local $/ = \1; # read one byte at a time

  while( my $byte = <$file> ){
    push @data, unpack('C',$byte);
  }
  close( $file );
}

print join(',', @data), "\n";

{
  open( my $file, '>', 'Some_Idx_Mod_mul_2.bin' );

  # You have two options
  for my $byte( @data ){
    print $file pack 'C', $byte * 2;
  }
  # or
  print $file pack 'C*', map { $_ * 2 } @data;

  close( $file );
}

答案 8 :(得分:0)

对于GCC :: TranslationUnit示例,请参阅http://gist.github.com/395160中的hparse.pl 这将使它成为C :: DynaLib,还有尚未编写的Ctypes。 这解析了FFI的函数,而不是与Convert :: Binary :: C相反的裸结构。 如果用作func args,hparse只会添加结构。