如何从用户输入修改C ++代码

时间:2015-08-04 16:12:09

标签: c++ clang root-framework

我目前正在编写一个位于C ++解释器之上的程序。用户在运行时输入C ++命令,然后将其传递给解释器。对于某些模式,我想用修改后的表单替换给出的命令,以便我可以提供其他功能。

我想替换

形式的任何内容
A->Draw(B1, B2)

MyFunc(A, B1, B2).

我的第一个想法是正则表达式,但这很容易出错,因为AB1B2中的任何一个都可以是任意的C ++表达式。由于这些表达式本身可能包含带引号的字符串或括号,因此将所有情况与正则表达式匹配将非常困难。此外,此表达式可能有多种嵌套形式

我的下一个想法是将clang称为子进程,使用" -dump-ast"获取抽象语法树,修改它,然后将其重建为要传递给C ++解释器的命令。但是,这需要跟踪任何环境更改,例如包含文件和转发声明,以便为clang提供足够的信息来解析表达式。由于翻译不公开这些信息,这似乎也是不可行的。

第三个想法是使用C ++解释器自己的内部解析转换为抽象语法树,然后从那里构建。但是,这个解释器并没有以任何我能找到的方式揭示ast。

是否有任何关于如何继续进行的建议,无论是沿着其中一条规定的路线,还是完全沿着不同的路线?

6 个答案:

答案 0 :(得分:3)

你想要的是Program Transformation System。 这些工具通常允许您表达对源代码的更改,这些源代码基本上表示源代码模式:

 if you see *this*, replace it by *that*

但是在抽象语法树上运行,因此匹配和替换过程是 比你用字符串黑客得到的更值得信赖。

此类工具必须具有感兴趣的源语言的解析器。 源语言是C ++使得这相当困难。

Clang有资格;毕竟它可以解析C ++。 OP对象 没有所有的环境背景,它就无法做到。的程度 OP正在输入(格式正确)程序片段(语句等)。 在翻译中,Clang可能[我没有多少经验 我自己难以专注于片段是什么(语句?表达式?声明?...)。最后,Clang并不是真正的PTS;它的树修改过程不是源到源的转换。这对方便起见,但可能无法阻止OP使用它;表面语法重写规则很方便,但您总是可以更省力地替换程序树黑客。当有不止一些规则时,这开始变得很重要。

GCC with Melt有点像Clang那样有资格。 我的印象是,Melt充分利用了GCC 这种工作无法容忍。 YMMV。

我们的DMS Software Reengineering Toolkit及其full C++14 [EDIT July 2018: C++17] front end绝对符合资格。 DMS已被用于进行大规模转换 在大规模的C ++代码库上。

DMS可以parse arbitrary (well-formed) fragments of C++而不事先告知语法类别是什么,并使用其模式解析机制返回正确语法非终结类型的AST。 [您最终可能会进行多次解析,例如歧义,您将决定如何解决,请参阅Why can't C++ be parsed with a LR(1) parser?以获取更多讨论]如果您愿意在解析时没有宏扩展而不依赖于“环境”,那么它可以做到这一点,并坚持预处理器指令(它们也被解析)在代码片段方面结构很好(#if foo {#endif不允许),但这不太可能成为交互式输入代码片段的真正问题。

然后,DMS提供了一个完整的程序AST库,用于操作已解析的树(搜索,检查,修改,构建,替换),然后可以从修改后的树中重新生成表面源代码,从而提供OP文本 提供给口译员。

在这种情况下,OP可能会直接将其大多数修改写为源到源语法规则。为他的 例如,他可以为DMS提供重写规则(未经测试但非常接近右侧):

rule replace_Draw(A:primary,B1:expression,B2:expression):
        primary->primary
    "\A->Draw(\B1, \B2)"     -- pattern
rewrites to
    "MyFunc(\A, \B1, \B2)";  -- replacement

和DMS将取代包含左侧“... Draw ...”模式的任何已解析的AST,并在将匹配替换为A,B1和B2之后用右侧替换该子树。引号是 metaquotes ,用于区分C ++文本和规则语法文本;反斜杠是metaquotes中用于命名metavariables的metaescape。有关您在规则语法中可以说的内容的更多详细信息,请参阅DMS Rewrite Rules

如果OP提供此类规则的 set ,则可以要求DMS应用整个集合。

所以我认为这对OP来说效果很好。这是一个相当重量级的机制,可以“添加”到他想要提供给第三方的包裹中; DMS及其C ++前端几乎不是“小”程序。但是现代机器有很多资源,所以我认为这是一个OP需要做多少的问题。

答案 1 :(得分:0)

尝试修改标题以压缩方法,然后编译你会发现错误并且能够替换所有核心。

至于你有一个C ++解释器(作为CERN的Root),我猜你必须使用编译器拦截所有的Draw,一个简单而干净的方法就是在标题中将Draw方法声明为私有,使用一些定义

 class ItemWithDrawMehtod
 {
 ....
 public:
 #ifdef CATCHTHEMETHOD
     private:
 #endif
 void Draw(A,B);
 #ifdef CATCHTHEMETHOD
     public:
 #endif
 ....
 };

然后编译为:

 gcc -DCATCHTHEMETHOD=1 yourfilein.cpp

答案 2 :(得分:0)

如果用户想要向应用程序输入复杂的算法,我建议将脚本语言集成到应用程序中。这样用户就可以编写代码[函数/算法以定义的方式],以便应用程序可以在解释器中执行它并获得最终结果。例如:Python,Perl,JS等。

由于您需要在解释器http://chaiscript.com/中使用C ++,这是一个建议。

答案 3 :(得分:0)

当有人获得Draw成员函数(auto draw = &A::Draw;)然后开始使用draw时会发生什么?据推测,你也希望在这种情况下调用同样改进的Draw功能。因此,我认为我们可以得出结论,您真正想要的是用您自己的函数替换Draw成员函数。

由于您似乎无法直接修改包含Draw的类,因此解决方案可能是从A派生您自己的类并在其中覆盖Draw。然后,您的问题就会减少,让您的用户使用新的改进类。

您可能会再次考虑自动将类A的使用转换为新的派生类的问题,但如果没有完整的C ++实现的帮助,这似乎仍然很难。也许有一种方法可以隐藏A的旧定义,并通过巧妙使用头文件来替换该名称,但我无法确定是否就是您告诉我们的情况。

另一种可能性是使用LD_PRELOAD来使用一些动态链接器hackery来替换在运行时调用的函数Draw

答案 4 :(得分:0)

可能有一种方法可以通过正则表达式完成此任务。

由于Draw之后出现的任何内容(已经正确格式化为参数,因此您不需要为了概述的目的完全解析它们。

从根本上说,重要的部分是" SYMBOL-> Draw("

SYMBOL可以是解析为重载对象的任何表达式 - >或者实现Draw(...)的类型的指针。如果将此减少为两种情况,则可以快速解析。

对于第一种情况,一个简单的正则表达式,用于搜索任何有效的C ++符号,类似于" [A-Za-z _] [A-Za-z0-9 _ \。]",以及文字表达式" - > Draw("。这将为您提供必须重写的部分,因为此部分后面的代码已经格式化为有效的C ++参数。

第二种情况是针对返回重载对象或指针的复杂表达式。这需要更多的努力,但是只需简单地编写复杂表达式的简短解析例程就可以轻松编写,因为您不必支持块(C ++中的块不能返回对象,因为lambda定义不会自己调用lambda,实际的嵌套代码块{...}不能直接返回内联的任何内容(这里适用)。请注意,如果表达式没有结束)那么它必须是此上下文中的有效符号,因此如果您发现a)只是匹配嵌套的)(并提取嵌套的SYMBOL之前的符号(...)( ...)...) - > Draw()模式。这可以通过正则表达式实现,但在普通代码中也应该相当容易。

只要你有符号或表达式,替换就很简单,从

开始

符号 - >绘制(...

YourFunction(SYMBOL,...

无需处理Draw()的其他参数。

作为一个额外的好处,使用此模型可以免费解析链式函数调用,因为您可以递归迭代代码,例如

A->Draw(B...)->Draw(C...)

第一次迭代识别第一个A-> Draw(并将整个语句重写为

YourFunction(A, B...)->Draw(C...)

然后识别第二个 - >使用表达式" YourFunction(A,...) - >"在它之前,并将其重写为

YourFunction(YourFunction(A, B...), C...)

其中B ...和C ...是格式良好的C ++参数,包括嵌套调用。

如果不知道解释器支持的C ++版本,或者您将要重写的代码类型,我真的无法提供任何可能值得的示例代码。

答案 5 :(得分:-1)

一种方法是将用户代码加载为DLL(类似于插件) 这样,您就不需要编译实际的应用程序,只需编译用户代码,您的应用程序就会动态加载它。