我目前正在编写一个位于C++解释器之上的程序。用户在运行时输入C++命令,然后将其传递到解释器中。对于某些模式,我想用修改后的形式替换给定的命令,以便提供其他功能。
我想替换任何形式的
A->Draw(B1, B2)
与
MyFunc(A, B1, B2).
我的第一个想法是正则表达式,但是这很容易出错,因为
A
,B1
或B2
中的任何一个都可以是任意C++表达式。由于这些表达式本身可以包含带引号的字符串或括号,因此很难将所有情况与正则表达式进行匹配。此外,此表达式可能有多种嵌套形式我的下一个想法是将clang称为子进程,使用“-dump-ast”获取抽象语法树,对其进行修改,然后将其重建为要传递给C++解释器的命令。但是,这将需要跟踪任何环境更改,例如包含文件和前向声明,以便为clang提供足够的信息来解析表达式。由于解释器不会公开此信息,因此这似乎也不可行。
第三种想法是使用C++解释器自己的内部解析将其转换为抽象语法树,然后从那里进行构建。但是,该解释器没有以我能够找到的任何方式公开ast。
是否有关于沿既定路线之一或完全沿另一条路线进行的任何建议?
最佳答案
您想要的是Program Transformation System。
这些工具通常可以让您表达对源代码的更改,这些更改以源代码级模式编写,本质上说:
if you see *this*, replace it by *that*
但对抽象语法树进行操作,因此匹配和替换过程为
比您获得的字符串黑客攻击更值得信赖。
此类工具必须具有用于感兴趣的源语言的解析器。
由于源语言是C++,因此相当困难。
lang语的资格;毕竟它可以解析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
在将匹配项替换为A,B1和B2之后,DMS将采用任何包含左侧“... Draw ...”模式的已解析AST,并将该子树替换为右侧。引号是元引号,用于区分C++文本和规则语法文本。反斜杠是在元引号内用于命名元变量的元转义符。有关规则语法中可以说的内容的更多详细信息,请参见DMS Rewrite Rules。
如果OP提供了一组此类规则,则可以要求DMS应用整个规则。
因此,我认为这对OP来说很好。 “添加”他要提供给第三方的包裹是一种相当重要的机制; DMS及其C++前端几乎不是“小”程序。但是现代机器拥有大量资源,因此我认为这是OP需要这样做的严重程度的问题。