C预处理器的替代方案

45

我有兴趣使用除了 C 预处理器以外的东西来预处理我的C和Objective-C源代码。是否有好的替代品?

例如,有些工具能够在C代码中间跳出到Python或Perl代码片段,并将输出的C代码编译成正常的代码。


1
你能否提供一个具体的例子,展示你希望在编译时生成源代码的方式(以及为什么)?这可能有助于我们提出针对你特定需求的建议。(例如,我猜想C++模板对你的情况不适用,但最好确认一下。) - reuben
2
不错的想法:这里有一篇IBM文章,作者使用Perl脚本生成查找表。http://www.ibm.com/developerworks/linux/library/l-metaprog1/index.html - user295190
13个回答

78
您可以将PHP用作C预处理器。优点如下:
  • 语法非常相似,因此语法高亮工作正常。
  • <??>在标准C中不被使用(使用非标准C时,唯一会出问题的是旧版GCC扩展运算符返回min/max)
  • 它丰富的库。
  • 它是图灵完备的。
  • 使用的宏是非常明确的。(与狡猾的C预处理器宏相比)

但对于严肃的用途,让PHP打印#line指令以调试预处理代码是必要的。

<?php include_once "stdio.h"; ?>

int main()
{
    <?php
        for($i = 0; $i < 20; $i++)
            echo 'printf("%d\n", '.$i.');';
    ?>
}

6
那很迷人。 - Félix Adriyel Gagnon-Grenier
53
请记住,现在这个消息正在Twitter、Reddit等平台上扩散。但是请大家记住:仅仅因为你“能够”或“曾经能够”,并不意味着你“应该”。 - Esko
2
@Esko:实际上,如果你想在编译期间动态生成代码,我认为这是一个很棒的想法——特别是如果C预处理器不足以满足你的需求。 - thejh
1
哇,这真是糟糕透了。 - Warren P
8
我终于找到了缩写词的真正含义:"P"HP "H"eader "P"reprocesser。它是一种完美的工具,可以预处理.h文件。 - Star Brilliant
这是在Mac上尝试的方法:将上述文件保存为try.php,然后在终端中运行php -d include_path=/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/ try.php > try.c - undefined

11

Cog 并不是一个预处理器,但它可以内嵌在代码中,并且动态生成一些内容。


1
你能否扩展一下你的回答(我相信这是最好的答案),给出一些例子(否则我会自己想)? - Xavier Combelle

10

2
对我来说,m4感觉太像一个科学项目,而不是一个预处理器语言。上次我看的时候,它没有循环结构,而是有关于如何使用递归来创建循环的指令。我认为这清楚地表明作者们太聪明了,超出了我的理解范围。 - Michael Kohne
1
循环结构,像这些吗?[http://www.gnu.org/software/m4/manual/html_node/Forloop.html#Forloop][http://www.gnu.org/software/m4/manual/html_node/Foreach.html#Foreach] - Hasturkun
3
m4 真的非常灵活,但语法太过艰深。我真希望我们有一个更现代化的Unix宏处理器,不过最好是像备受尊敬的 m4 那样由C编译而成。 - J. M. Becker
2
我写了一篇关于如何在C语言中使用m4的博客文章,或许可以帮助一些人入门:http://kvanberendonck.id.au/using-m4-with-c/ - kvanbere
@Pacerier,你是指用例还是一般情况?我是一般性地说,对于这种用例,所有这些选项都可以胜任。一般来说,我更需要一种通用但现代的宏语言。这种语言应该特定于这个极其常见的领域,但实际上并不需要脚本编写。 - J. M. Becker
显示剩余2条评论

9
你运行代码,然后将结果拼接起来的想法被称为准引用。你运行的代码是反引用的。
我知道如何使用Lua解决这个问题。我使用了string.gsub和我自己编写的反引用函数。我使用了shell语法进行反引用。就像在shell中一样,反引用的代码返回一个字符串,然后被拼接到代码中。
下面的prog是带有反引用文本的C代码,antiquote是反引用函数。我充分利用了Lua的特殊字符串引用双方括号。实际上,你不会这样做;你会把prog放在一个单独的文件中。
names = { 'John', 'Paul', 'George', 'Ringo' }

local prog = [===[
#include <stdio.h>

main() {
  $(local out = { }
    for _, n in ipairs(names) do
      table.insert(out, string.format([[  printf("The name is %%s\n", %q);]], n))
    end
    return table.concat(out, '\n  ')
   )
}
]===]


local function antiquote(s)
  local body = s:match '^%$%((.*)%)$'
  return assert(loadstring(body))()
end

prog = prog:gsub('%$%b()', antiquote)
io.stdout:write(prog)

在使用时,该程序的界面如下所示:
: nr@curlycoat 1181 ; lua /home/nr/tmp/emit-c.lua
#include <stdio.h>

main() {
    printf("The name is %s\n", "John");
    printf("The name is %s\n", "Paul");
    printf("The name is %s\n", "George");
    printf("The name is %s\n", "Ringo");
}

6
当然,标准的C预处理器非常有限。
最近我完成了这样一个工具:https://github.com/d-ash/perlpp
举个例子:
<?
    my @types = ('char', 'int', 'long'); 
    foreach (@types) {
?>
        <?= $_ ?> read_<?= uc($_) ?>(<?= $_ ?>* v);
<?  } ?>

变成这样

char read_CHAR(char* v);
int read_INT(int* v);
long read_LONG(long* v);

语法类似于PHP,但是它使用的是Perl,并且可以将文本捕获到Perl字符串中。

编辑者cxw — 在@d-ash的批准下,我也是perlpp的维护人员。如果您有问题,请随时联系我!


1
你应该添加一个关于它如何工作的示例。 - António Almeida
这对我非常有效,感谢您接受我的拉取请求! - cxw

5
如果你愿意动手使用一些C++,那么Boost库中的Wave解析器是一个不错的选择,它是使用Spirit递归下降解析器构建的完整的C预处理器,并符合最新的C和C++规范(顺带一提,也符合Objective C的规范)。该解析器高度模块化,因此您可以自己切换驱动程序来执行额外功能。详见http://www.boost.org/libs/wave/doc/introduction.html

更新链接: http://www.boost.org/doc/libs/1_54_0/libs/wave/doc/introduction.html永久链接: http://www.boost.org/libs/wave/doc/introduction.html - Rhubbarb

5
如果您稍微抽象一下问题,那么实际上您正在寻找一个用于代码的模板引擎。就像大多数网站将动态生成的内容插入静态模板一样,您想要将动态生成的代码插入到程序中。
我目前在大多数模板工作中使用 Jinja2(Python)- 我发现它在各个方面都非常可配置。

4
CPP为C代码提供了许多重要的功能,您可能不需要重新实现这些功能。相反,您似乎正在寻找一种模板处理过程,该过程会发出C代码。 Cheetah只是许多允许您使用Python的模板之一。还有其他使用Python的模板,以及其他语言中的更多模板,但是Cheetah以其输出无关性而闻名,而某些模板引擎则非常重视HTML/XML。请做好调查工作。

3

我曾经思考过这个问题。确保你能接受这样一个事实,那就是任何想要编译你的代码的人都需要新的预处理工具。如果只有你一个人会使用它,那么没有问题,但是如果你想让代码对其他人可用,那么你可能需要考虑是否添加工具要求是一个好主意。


我并不确定我是否满意。但我很感兴趣去看看。 :-) - Ken
@Ken,这个考虑并不是那么简单明了的。例如...你有多少次解压tar.gz并运行./configure?所有用于重新生成的源代码都在那里,但你没有必要去追寻正确版本的automake/autoconf。所以你所需要做的就是提供原始代码和已经处理过的版本。 - J. M. Becker
@TechZilla - 坦白说,那不是世界上最好的想法。如果您提供已经处理过的代码,那么人们最终会修改已经处理过的代码,除非您真的很幸运,否则有人会修改生成的部分(尽管有“不要修改-它是生成的!”警告)。然后,如果您需要在以后处理该代码,则会面临严重的问题。 - Michael Kohne
2
@MichaelKohne:我并不否认这个担忧,但这并不是你最初提到的问题,“确保你对于任何想要编译你的代码的人都需要新的预处理工具这一事实感到满意。”另外,你提到的这个新的担忧,同样适用于我的automake/makefile类比。每个依赖automake的项目也需要解决这些相同的问题,这是我最初的观点。提供两者的原因,就像依赖automake的项目一样,是为了普通的编译终端用户不需要代码生成工具。 - J. M. Becker

3
短答案是“不行”。预处理器与C语言的语义紧密相连,因此您无法真正删除它。实际上,在某些编译器中,预处理器甚至不再像早期那样是一个单独的阶段——在Mac上编译Objective C只需解析Objective C语法。因此,虽然您可以使用另一个宏处理器(比如m4)来处理源文本,然后将其传递给C编译器,但您并不能消除C预处理器,而只是增加了另一步预处理过程。
但这里有一个更深层次的问题:通过消除CPP阶段,您想要获得什么?

我同意他应该补充而不是替代CPP。然而,我不同意预处理是由编译器强制执行的。至少GCC提供了一种跳过预处理阶段的方法。 - strager
我并不期望消除cpp阶段——增加一个额外的阶段是可以的。我对替代方案感兴趣,因为cpp受限且充满陷阱。没有循环,没有数组——任何复杂的东西在cpp中可能只是个坏主意。 - Ken
那我真的会有诱惑用像 Perl 这样的东西,并完成它。M4 当然可以做到——它是图灵完备的——但它很难懂且让人感到神秘。即使与 Perl 相比。 - Charlie Martin
CPP不能递归,例如,语法很糟糕。boost::preprocessor使事情变得更容易,但仍然很困难和繁琐,并且递归问题很难解决。 - chila
我认为我们有点偏离了问题。上帝知道比CPP更流畅的预处理器是什么。该死,UNIX在无数年前就有M4了。但是,除非#define和#include不再出现在代码中,否则您无法消除CPP,即使您不使用它们,CPP仍将在软件包中存在。 - Charlie Martin
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接