【问题标题】:How can I parse a C header file with Perl?如何用 Perl 解析 C 头文件?
【发布时间】:2009-06-15 06:33:14
【问题描述】:

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

例如,我有一些结构,例如

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 之类的其他东西也可以。

谁能帮忙!!!

【问题讨论】:

  • 有什么理由不使用 C 语言吗?我认为这是解决这个问题的合乎逻辑的方法。
  • 您只是在读取和修改 C 标头吗?为什么?
  • @Ryan,你能不能再详细一点。你是说我可以在C内部使用相同的头文件,然后用它来解析? @Carson,实际上,我只是举了一个例子,我需要做分裂、合并之类的事情。
  • 是的,但是,这些数据的目的是什么...例如,您是否通过在标题中自动声明或其他方式来为自己节省工作?为什么要解析头部而不是动态创建这些数组?
  • @Carson,头文件数据最终会被制作成硬件的ROM数据。这需要一些特定的格式,适合 ROM 表的生成。

标签: python c perl parsing header-files


【解决方案1】:

将数据保留在标题中会使使用 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;
  }

【讨论】:

    【解决方案2】:

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

    【讨论】:

    • @Neil,头文件数据最终会被制作成硬件的ROM数据。这需要一些适合 ROM 表生成的特定格式。而且有很多不同变量名的头文件,我不确定它们是否可以在运行时包含。
    • 我的意思不是在你的真实程序中包含头文件,我的意思是编写另一个程序来处理头文件,但是用 C 而不是 Perl 或 Python 编写该程序。
    • @Neil,如果我们有几个 h(eader) 文件,我认为你的想法会很好。但是我们这里有大约 100 多个头文件,每个头文件平均有大约 10 个不同的数据结构定义。我们认为,将所有这些包含在 C 文件中将是一项艰巨的任务。我们计划应用的解析公式对所有文件都是相同的,所以我们认为解析(可能)是一个好主意。但是,如果没有其他方法可行,我们将使用 C 来实现它。
    • 你也可以写一个写C程序的程序。
    【解决方案3】:

    您并没有真正提供有关如何确定要修改的内容的太多信息,而是针对您的具体示例:

    $ 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 是应该被替换的数字。

    【讨论】:

      【解决方案4】:

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

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

      【讨论】:

        【解决方案5】:

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

        现在,我认为这对您而言并不重要,但是使用 Parse::RecDescent 的递归下降解析器在算法上比 Parse::YappParse::EYapp 等工具在算法上要慢(我认为是 O(n^2)) .我没有检查 Parse::EYapp 是否带有这样的 C-parser 示例,但如果是这样,那是我建议学习的工具。

        【讨论】:

          【解决方案6】:

          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()
          

          【讨论】:

            【解决方案7】:

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

            【讨论】:

              【解决方案8】:

              您始终可以使用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 );
              }
              

              【讨论】:

                【解决方案9】:

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

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2016-04-08
                  • 2012-03-27
                  • 2016-01-22
                  • 2012-06-08
                  • 2019-10-10
                  相关资源
                  最近更新 更多