Как я могу разобрать файл заголовка C с Perl? - PullRequest
6 голосов
/ 15 июня 2009

У меня есть заголовочный файл, в котором есть большая структура. Мне нужно прочитать эту структуру с помощью некоторой программы, выполнить некоторые операции над каждым членом структуры и записать их обратно.

Например, у меня есть такая структура, как

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 ]

9 голосов
/ 15 июня 2009

Хранение ваших данных в заголовке затрудняет использование других программ, таких как 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, используя модуль CPAN с именем GCC :: TranslationUnit . Вот команда 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;
  }
6 голосов
/ 15 июня 2009

Извините, если это глупый вопрос, но зачем вообще беспокоиться о разборе файла? Почему бы не написать программу на C, которая #include заголовок, обрабатывает его как требуется, а затем выплевывает источник для измененного заголовка. Я уверен, что это будет проще, чем решения Perl / Python, и будет гораздо надежнее, потому что заголовок будет анализироваться синтаксическим анализатором Си.

4 голосов
/ 15 июня 2009

Вы на самом деле не предоставляете много информации о том, как должно быть изменено то, что должно быть изменено, но для решения вашего конкретного примера:

$ 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 голосов
/ 15 июня 2009

Если все, что вам нужно сделать, это изменить структуры, вы можете напрямую использовать регулярное выражение для разделения и применения изменений к каждому значению в структуре, ища объявление и окончание}; знать, когда остановиться.

Если вам действительно нужно более общее решение, вы можете использовать генератор парсера, например PyParsing

2 голосов
/ 15 июня 2009

Существует действительно полезный модуль Perl Convert :: Binary :: C , который анализирует заголовочные файлы C и преобразует структуры из / в структуры данных Perl.

2 голосов
/ 15 июня 2009

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()
2 голосов
/ 15 июня 2009

Существует модуль Perl Parse :: RecDescent , который является очень мощным генератором синтаксического анализатора рекурсивного спуска. Это идет с кучей примеров. Одним из них является грамматика, которая может анализировать C .

Теперь, я не думаю, что это имеет значение в вашем случае, но парсеры рекурсивного спуска, использующие Parse :: RecDescent, алгоритмически медленнее (я думаю, O (n ^ 2)), чем такие инструменты, как Parse :: Yapp или Parse :: EYapp . Я не проверял, поставляется ли Parse :: EYapp с таким примером C-parser, но если так, то этот инструмент я бы рекомендовал изучить.

0 голосов
/ 10 мая 2010

Для примера GCC :: TranslationUnit см. Hparse.pl из http://gist.github.com/395160 который превратит его в C :: DynaLib, а также еще не написанные Ctypes. Это анализирует функции для FFI, а не простые структуры, противоречащие Convert :: Binary :: C. hparse будет добавлять структуры, только если они используются в качестве аргументов func.

0 голосов
/ 15 июня 2009

Вы всегда можете использовать 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 );
}
...