【问题标题】:How to create a refactor tool?如何创建重构工具?
【发布时间】:2021-11-05 10:08:27
【问题描述】:

我想学习如何创建重构工具。

例如:我想创建迁移脚本
当某些库删除不推荐使用的函数时
我们希望转换代码以使用更新的足够功能。

我的想法是使用 ANTLR 将代码解析为 AST,
在这棵树上使用一些模式匹配来修改内容,
并输出修改后的内容。

但是,根据我的阅读,ANTLR 并未在 AST 树中保留格式,
因此很难恢复完整的内容。

您是否有符合以下要求的解决方案:

  • 允许我使用保留格式修改代码
  • (可选)允许我使用 AST 转换进行代码转换
  • (可选)可以转换多种语言,例如 ANTLR

问题不限于一种特定的语言,
我很高兴听到针对不同语言创建的解决方案。

【问题讨论】:

    标签: automation refactoring antlr abstract-syntax-tree


    【解决方案1】:

    如果你想要一个

    • 从任意语言解析源代码的通用工具生成 ASTs
    • 应用程序或最好源到源模式导向重写规则来操作 AST
    • 重新生成有效的源代码保留格式和 cmets

    我知道目前只有两个系统可以稳健地做到这一点

    您可能不想自己尝试构建这样的框架;这些工具都拥有数十年的博士水平投资,以使其实用。

    一个反复出现的问题是错误地认为拥有解析器(例如 ANTLR)可以解决大部分问题。请参阅我在Life After Parsing. 上的文章 一个关键的见解是,如果没有上下文,您无法转换“仅语法(AST)”;您必须考虑语言语义,并且您选择使用的任何框架都可以更好地帮助您进行语义分析以支持重写规则。

    还有其他(general purpose) program transformation systems。很多都是研究。在实践中很少有人被用来进行认真的软件再工程。 RASCAL 已应用于一些非常有趣的任务,但在我所知道的任何商业环境中都没有。 DMS 已在生产环境中使用了 20 多年,用于执行大规模的代码库更改,包括重构、API 修订和全自动语言迁移。

    【讨论】:

      【解决方案2】:

      ANTLR 有一个 TokenStreamRewriterTokenStreamRewriter 类,它非常擅长保存您的源输入。

      它具有非常强大的功能。它允许您删除、插入或替换输入流中的文本。 IT 实际上存储了一系列待处理的更改,然后在您请求修改后的输入流时应用它们(甚至允许回滚更改以及多组更改)。

      我最近所做的一次演示中涉及重写器的几个示例:

         private void plus0(RefactorUtilContext ctx, String pName) {
              for (var match : plus0PatternA.findAll(ctx, ANY_EXPR_XPATH)) {
                  var matchCtx = (AddSubExprContext) (match.getTree());
                  rewriter.delete(pName, matchCtx.op, matchCtx.rhs.getStop());
              }
              for (var match : plus0PatternB.findAll(ctx, ANY_EXPR_XPATH)) {
                  var matchCtx = (AddSubExprContext) (match.getTree());
                  rewriter.delete(pName, matchCtx.lhs.getStart(), matchCtx.op);
              }
          }
      
          private void times1(RefactorUtilContext ctx, String pName) {
              for (var match : times1PatternA.findAll(ctx, ANY_EXPR_XPATH)) {
                  var matchCtx = (MulDivExprContext) (match.getTree());
                  // rewriter.delete(pName, matchCtx.op, matchCtx.rhs.getStop());
                  rewriter.insertBefore(pName, matchCtx.op, "/* ");
                  rewriter.insertAfter(pName, matchCtx.rhs.getStart(), " */");
              }
              for (var match : times1PatternB.findAll(ctx, ANY_EXPR_XPATH)) {
                  var matchCtx = (MulDivExprContext) (match.getTree());
                  // rewriter.delete(pName, matchCtx.lhs.getStart(), matchCtx.op);
                  rewriter.insertBefore(pName, matchCtx.lhs.getStart(), "/* ");
                  rewriter.insertAfter(pName, matchCtx.op, " */");
              }
          }
      

      TokenStreamRewriter,基本上,只是存储了一组关于如何修改输入流的指令,所以关于你输入流的所有内容,你不修改的是,嗯,未修改:)。

      您可能还希望查看 ANTLR 拥有的XPath capabilities。这些允许您在解析树中找到非常具体的模式来定位您想要重构的部分。顾名思义,其语法与 XML 文档的 XPath 非常相似,但在解析树而不是 XML DOM 上工作。

      注意:所有这些都在解析树上运行,而不是 AST(这必然是您自己的设计,因此 ANTLR 不会知道它的结构。此外,AST 的本质是丢弃不相关的信息(像 cmets、空格等),所以它们不是您想要保留格式的任何内容的良好起点。)

      我为此演示文稿整理了一个很小的项目,它位于 GitHub 上的 LittleCalc。最相关的文件是LittleCalcExecutionVisitor

      您可以通过运行 LittleCalcRepl.java 来启动 REPL 并进行测试

      【讨论】:

        猜你喜欢
        • 2017-09-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-12-23
        • 1970-01-01
        • 2016-07-16
        • 2018-05-30
        相关资源
        最近更新 更多