【问题标题】:python remove C function bodypython删除C函数体
【发布时间】:2012-05-06 16:25:10
【问题描述】:

我正在寻找如何从某些 C 源文件中的函数中删除整个身体的方法。

例如我有这个内容的文件:

1.  int func1 (int para) {
2.    return para;
3.  }
4.
5.  int func2 (int para) {
6.    if (1) {
7.      return para;
8.    }
9.    return para;
10. }

我已经尝试过这些正则表达式:

content = re.sub('(\{[.*]?\})', '', content, flags=re.DOTALL)

但是嵌套的 { } 有问题。此正则表达式仅替换第一个 },因此第 9 行和第 10 行仍在内容中。我认为解决方案应该是计算 { 和 } 括号并在计数器为 0 时停止替换。{ 被发现 => counter++,} 被发现 => counter--。但我不知道如何在 python 中实现它。你们可以给我一脚吗?

【问题讨论】:

  • 选择真正的解析器from some available options 可能会更好地为您服务——通过正则表达式解析 C 注定会令人沮丧和烦恼。我希望你可以让它适用于简单的玩具,但任何真正的代码库都可能会使用 some 构造,你无法用正则表达式复制。
  • 出于好奇,您为什么要这样做?如果没有更好的方法来解决您的实际问题,我会感到非常惊讶。
  • 我真正的问题是获取每个函数的返回类型、名称和参数列表,所以在匹配之前我想从源文件中删除所有无用的信息。例如宏、cmets 和实体。但是嵌套问题困扰着我。
  • 看看这个问题,你必须使用递归。 stackoverflow.com/questions/10318351/…
  • 理论说用正则表达式不可能做你想做的事。正则表达式处理正则语言(这就是它们的名字的来源)。具有匹配分隔符对的语言是不规则的。它充其量是上下文无关的(一大类语言)。您需要一个实际的解析器(请参阅@sarnold 的出色答案)。

标签: python c regex match substitution


【解决方案1】:

我认为您正在尝试重新发明一个之前已经实施过多次的轮子。如果您只想提取 C 文件中每个函数的签名,那么有更简单的方法来完成。

ctags 实用程序将为您解决这个问题:

~/test$ ctags -x --c-types=f ./test.c
func1            function      1 ./test.c         int func1 (int para) {
func2            function      5 ./test.c         int func2 (int para) {
~/test$ # Clean up the output a little bit
~/test$ ctags -x --c-types=f ./test.c | sed -e 's/\s\+/ /g' | cut -d ' ' -f 5-
int func1 (int para) {
int func2 (int para) {

【讨论】:

  • 同意。这个问题在这个question中得到了很好的处理
  • 为了实习,我在 Python 中进行了六个月的 C 静态分析,我非常同意作者对这个问题的看法。否则,您可能会走上解析和语法树的道路,或者设计一个错过一些奇怪边缘情况的笨拙的正则表达式解决方案。使用 ctags,不要重新发明轮子。
  • 这很聪明——充分利用另一个已经很好地解析 C 的工具。很好。
  • 我正在使用最新版本的 ctags,但出现此错误:ctags: unrecognized option '--c-types=f' Try ctags --help' 以获得完整的选项列表。`
  • @Soubriquet 我相信较新的版本可能已将命令行选项中的“类型”一词更改为“种类”,但语法是相同的。
【解决方案2】:

这是我从 C 源文件中删除函数体的脚本之一。唯一的要求是来自 Mac OSX 中 brew 的 ctags,而不是 Mac OSX 中内置的 ctags。我不确定为什么它不适用于 Mac OSX 中的内置 ctags。您可以使用brew 输入命令来安装 ctags:

$ brew install ctags

然后,将以下名为 dummyc.pl 的 PERL 脚本与 C 源文件一起使用。比如输入C源码:

int
func1 (int para)
{
  return para;
}

int
func2 (int para)
{
  if (1)
    {
      return para;
    }
  return para;
}

这是输出:

int
func1 (int para)
{
  return 0;
}

int
func2 (int para)
{
  return 0;
}

这是 PERL 脚本:

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

unless ( @ARGV == 1 )
{
  print "Filter out the body of C functions.
Usage: dummyc.pl file.c
Required: ctags (e.g., \$ brew install ctags)\n";
  exit;
}

my $cfile = $ARGV[0];
my $lc = 1;
my $kindPrev = "comment";
my $lnPrev = 1;
my $lsPrev = "comment";
my $namePrev = "comment";
my $line = 1;
open(CFILE, $cfile) or die "could not open $cfile: $!";
open(PIPE, "/usr/local/Cellar/ctags/5.8/bin/ctags -xu $cfile|") or die "couldn't start pipe: $!";
while ($line)
{
  last unless $line;
  # R_USE_SIGNALS    macro        24 errors.c         #define R_USE_SIGNALS 1
  $line = <PIPE>;
  my $name;
  my $kind;
  my $ln;
  my $ls;
  if ($line)
  {
    $line =~ /^(\S+)\s+(\w+)\s+(\d+)\s+$cfile\s+(.+)/;
    $name = $1;
    $kind = $2;
    $ln = $3;
    $ls = $4;
  }
  else
  {
    $ln = 1000000;
  }

  if ($kindPrev eq "function") 
  {
    my $isFunctionBody = 0;
    my $hasStartBrace = 0;
    my $hasReturnValue = 1;
    my $noReturn = 0;
    for (my $i = $lnPrev; $i < $ln; $i++)
    {
      my $cline = <CFILE>;
      last unless $cline;

      if ($cline =~ /void.+$namePrev/)
      {
        $hasReturnValue = 0;  
      }
      if ($cline =~ /NORET.+$namePrev/)
      {
        $noReturn = 1;  
      }
      if ($isFunctionBody == 0 and $cline =~ /\{/)
      {
        $isFunctionBody = 1;
        unless ($cline =~ /^\{/)
        {
          $hasStartBrace = 1;
          print $cline;
        }
      }
      elsif ($cline =~ /^\}/)
      {
        $isFunctionBody = 0;
        print "{\n" if $hasStartBrace == 0;
        if ($noReturn == 0)
        {
          if ($hasReturnValue == 1)
          {
            print "  return 0;\n";
          }
          else
          {
            print "  return;\n";
          }
        }
      }
      unless ($isFunctionBody == 1)
      {
        print $cline;
      }
    }
  }
  else
  {
    for (my $i = $lnPrev; $i < $ln; $i++)
    {
      my $cline = <CFILE>;
      last unless $cline;
      print $cline;
    }
  }
  $kindPrev = $kind;
  $lnPrev = $ln;
  $lsPrev = $ls;
  $namePrev = $name;
}
close(PIPE) or die "couldn't close pipe: $! $?";
close(CFILE) or die "couldn't close $cfile: $! $?";

不过,您可能想要编辑 PERL 脚本。

【讨论】:

    【解决方案3】:

    这是一个纯python解决方案,实现起来非常简单。

    提取主体的函数

    基本上,您尝试将每个{ 与对应的} 匹配:

    • 如果在下一个} 之前有两个{,那么您正在进入一个范围。
    • 另一方面,如果在下一个{ 之前有一个},那么您将退出范围。

    然后实现是微不足道的:

    • 您在不同列表中查找 {} 的所有索引
    • 您还维护一个范围深度变量
      • 如果当前{ 位置低于当前} 位置,则您正在进入一个范围,将范围深度加1,然后移动到下一个{ 位置
      • 如果当前{ 位置高于当前} 位置,则您正在退出范围,您将1 移至范围深度并移至下一个} 位置
    • 如果作用域深度变量为0,那么你找到了函数体的右大括号

    假设你的字符串在你的函数体的第一个大括号之后开始(不包括大括号),使用这个子字符串调用以下函数会给你最后一个大括号的位置:

    def find_ending_brace(string_from_first_brace):
      starts = [m.start() for m in re.finditer('{', string_from_first_brace, re.MULTILINE)]
      ends = [m.start() for m in re.finditer('}', string_from_first_brace, re.MULTILINE)]
    
      i = 0
      j = 0
      current_scope_depth = 1
    
      while(current_scope_depth > 0):  
        if(ends[j] < starts[i]):
          current_scope_depth -= 1
          j += 1
        elif(ends[j] > starts[i]):
          current_scope_depth += 1
          i += 1
          if(i == len(starts)): # in case we reached the end (fewer { than })
            j += 1
            break
    
      return ends[j-1]
    

    提取候选函数定义

    现在,如果文件的原始字符串在变量my_content 中,

    find_func_begins = [m for m in re.finditer("\w+\s+(\w+)\s*\((.*?)\)\s*\{", my_content)]
    

    将为您提供每个函数的原型(find_func_begins[0].group(1) == func1find_func_begins[0].group(2) == 'int para'),以及

    my_content[
      find_func_begins[0].start():
        find_func_begins[0].end() +
        find_ending_brace(my_content[find_func_begins[0].end():])]
    

    会给你正文的内容。

    提取原型

    我想您应该在到达第一个结束大括号后再次查找函数定义,因为find_func_begins 的正则表达式有点松散。迭代每个函数定义并匹配大括号会产生以下迭代算法:

    reg_ex = "\w+\s+(\w+)\s*\((.*?)\)\s*\{"
    last = 0
    protos = ""
    find_func_begins = [m for m in re.finditer(reg_ex, my_content[last:], re.MULTILINE | re.DOTALL)]
    while(len(find_func_begins) > 0):
      function_begin = find_func_begins[0]
      function_proto_end = last + function_begin.end()
      protos += my_content[last: function_proto_end-1].strip() + ";\n\n"
    
      last = function_proto_end + find_ending_brace(my_content[function_proto_end:]) + 1
      find_func_begins = [m for m in re.finditer(reg_ex, my_content[last:], re.MULTILINE | re.DOTALL)]
    

    protos 中应该有你想要的。希望这会有所帮助!

    【讨论】:

      【解决方案4】:

      我需要清理它:)

      类 FuncBody(对象):

      def __init__(self):
      
          self.stack = []
      
      def stack_insert(self, sym_list):
      
          sym_list.sort(key=lambda x : x[1])
          #print "sym_list ", sym_list
      
          for sym, idx in sym_list:
              #print "here ", sym, idx
              if self.stack != [] and\
                      (self.stack[-1] == '{' and sym == '}'):
                  self.stack.pop()
              else:
                  self.stack.append(sym)
      
      def get_body(self, filepath, start):
      
          Begin = False
          self.stack = []
          for lineno in range(start, get_line_count(filepath)):
              #print lineno, getline(filepath, lineno)
              if False == Begin and\
                      '{' in getline(filepath, lineno):
                  self.stack_insert([('{', m.start())\
                          for m in re.finditer('{', getline(filepath, lineno))]+\
                          [('}', m.start()) for m in\
                          re.finditer('}', getline(filepath, lineno))])
                  #print "Begin"
                  Begin = True
                  yield getline(filepath, lineno)
                  continue
              if True == Begin and self.stack == []:
                  #print "End here"
                  break
              elif True == Begin:
                   self.stack_insert([('{', m.start())\
                          for m in re.finditer('{', getline(filepath, lineno))]+\
                          [('}', m.start()) for m in\
                          re.finditer('}', getline(filepath, lineno))])
      
              #print "stack ", self.stack
              yield getline(filepath, lineno)
      

      【讨论】:

        猜你喜欢
        • 2015-10-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多