【问题标题】:Tree-pattern matching of AST. Any clean way in C++?AST的树模式匹配。 C ++中有什么干净的方法吗?
【发布时间】:2014-09-09 21:40:41
【问题描述】:

上下文:我需要为抽象语法树编写一些树匹配规则。

我想要一种简洁的方式,例如,如果为数组访问提供了数字-文字索引(而不是符号),则进行匹配。

假设我有一个抽象类(即具有纯虚函数)lvaluelvalue 仅是 2 个具体类的子类,variablearray_element

为了以不同的方式处理这两种情况,我可以应用访问者模式(但我认为这里有点矫枉过正)或使用丑陋的 dynamic_cast 混乱。 (我已经使用访问者模式来遍历我的 AST 和 CFG)

void main() {
    lvalue *lv = new variable("foo");
    // ... somehow do a tree-pattern matching on lv
}

要检查 lv 是否是带有文字(即常量)索引的数组访问,我当然可以编写以下内容:

if (array_element *ae = dynamic_cast<array_element*>(lv)) {
   if (dynamic_cast<constant*>(ae->index)) {
      cout << "Yes, lv is an array-access and indexed by a literal" << endl;
   }
}

...但这该死的丑陋且无法维护。下面是朝着正确方向迈出的一步(如果它有效的话):

void func(variable *n) {
    cout << "got a variable" << endl;
}

void func(array_element *n) {
    cout << "got a array" << endl;
}

有什么办法可以避免dynamic_cast的混乱吗? 请指教:)

【问题讨论】:

  • 你可能想看看 LLVM 做了什么。它需要一些管道,它仍然是一种解决方法,但生成的匹配代码可能是你得到的最干净的代码(没有严重滥用预处理器)。

标签: c++ polymorphism pattern-matching abstract-syntax-tree


【解决方案1】:

Yuriy Solodkyy 目前正在开发一个名为Mach7 的 C++11 库,它提供了一个类似 switch 的语句来执行此操作。他的slidestype switchingpattern matching 上的论文(与Gabriel Dos Reis 和Bjarne Stroustrup 合着)详细介绍了性能和实施。

它让您可以简单地切换类型,这提供了访问者模式的替代方案。

Match(lv)
{
    Case(array_element)
        cout << "array_element" << endl;
    ...
}

令人印象深刻的是,它还可以让您进行更复杂的模式匹配,类似于 ML 或 Haskell:

Match(lv)
{
  Case(C<array_element>(C<constant>(index)))
      cout << "Yes, lv is an array-access and indexed by a literal" << endl;
  ...
}

另请参阅 Thomas Petit 的 answer 到 Skeen 的相关 question 关于在 C++ 中进行 ML 样式的模式匹配。

【讨论】:

    【解决方案2】:

    这可能取决于您的实际上下文...我确信您的真实代码正在做一些比

    更有趣的事情
    cout << "Yes, lv is an array-access and indexed by a literal" << endl;
    

    但我认为最简单的建议是使用普通的多态性。也就是说,在访问者模式试图模拟双重调度的情况下,你真正需要的(我认为)是单一调度。明显的开始是这样的:

    struct lvalue {
        virtual void policy() = 0;
    };
    
    struct variable : public lvalue {
        virtual void policy() { /* whatever */ }
    };
    
    struct array_element : public lvalue {
        virtual void policy() {
            cout << "Yes, lv is an array-access and indexed by a literal" << endl;
        }
    };
    

    但我确定这是您已经想出来的:P 您也可以考虑添加一个间接级别:

    // interface for a "policy" class.  this could look however you want it to.
    struct lvalue_policy {
        virtual void operator()() = 0;
    };
    
    struct lvalue {
        virtual lvalue_policy policy() = 0;
    };
    
    // variables
    struct variable_policy : public lvalue_policy {
        virtual void operator()() { /* whatever */ }
    };
    
    struct variable : public lvalue {
        virtual variable_policy policy() { return variable_policy; }
    };
    
    // array elements
    struct array_element_policy : public lvalue_policy {
        virtual void operator()() {
            cout << "Yes, lv is an array-access and indexed by a literal" << endl;
        }
    };
    
    struct array_element : public lvalue {
        virtual array_element_policy policy() { return array_element_policy; }
    };
    

    ...但我不确定这是否真的能比更简单的直接方法给你带来任何好处。我认为他们两个都有的问题是policy() 的返回类型不灵活——它们都归结为void 的返回。同样,这可能适合您的情况,但我的偏见总是尽可能倾向于编译时多态性,因为它为您提供了更大的类型灵活性。所以如果我是你,我会尝试的解决方案更像这样:

    template class lvalue<typename T, typename policyT> {
        policyT m_policy; // assume this has an operator() that returns T
    public:
        T policy() { return m_policy(); }
    };
    

    【讨论】:

      【解决方案3】:

      C++ 不是一种用这种表面语法模式进行模式匹配的好语言;充其量你必须编写复杂的访问者,并通过各种了解 AST 结构的检查。

      您可以考虑使用专为 AST 转换而设计的工具,例如源到源 Program Transformation" 工具。其中大部分将让您编写某种用于启用重写的表面语法模式,规则形式为“如果您看到 this syntax,将其替换为 that syntax”。

      现在,大多数此类工具都要求您以某种方式定义要对工具进行操作的语言,因此它知道该语法是什么。你没有说你想操纵哪种语言;您的 SO C++ 标签似乎在那里,因为您想在 C++ 中执行此操作。这部分我帮不了你 :-{ 如果你想操纵 C++,我不会感到惊讶,因为这就是你正在编码的内容,但这只是一个猜测。但是如果你要使用程序转换工具,你需要一个健壮的语言定义,而这些很难获得。很难获得用于此目的的 C++ 的良好定义,尤其是在 C++11 现已推出的情况下。

      我们的DMS Software Reengineering Toolkit 及其C++ Front End 可以为C++ 做到这一点。 DMS 提供对 AST 的解析、对 AST 的过程访问(就像您在 C++ 程序中所做的那样,但更重要的是,这种源模式概念。

      对于您的特定任务,您可以使用以下 DMS 模式。

       domain Cpp~gcc4;
      
       pattern numeric_literal_index(v: identifier, n: numeric_literal):lhs
          " \v[\n] ";
      

      语言和方言由 domain 声明指定。本例语言为C++(这里明显拼写为Cpp),方言为gcc4(DMS可以处理多种C++方言)。

      模式被命名(因为我们经常有很多模式和规则)*numeric_literal_index*。该模式由标识符(这是一个 C++ 语法标记)和 numeric_literal(同样,但这是一个允许任何无数 C++ 数字文字类型的语法非终结符)参数化,因为我们想要匹配 1、3L 等。 该模式被限制为匹配一个 lhs (一个 C++ 语法非终结符),尽管在实践中,由于它的语法,它不会匹配任何其他东西。实际的模式包含在 metaquotes "..." 中,它将 C++ 特定语法与模式语言语法的环绕海洋隔离开来。

      使用此模式,可以进行 DMS 调用以将此模式与树节点匹配。匹配将返回指向标识符 AST 节点和 numeric_literal 树节点的指针。

      当然,人们可以编写更复杂的模式,甚至可以使用它重写规则。一个道德:使用正确的工具来完成工作。

      【讨论】:

        【解决方案4】:

        我什至不确定它在 C 或 C++ 中是否可行,因为模式匹配涉及模式变量,并且没有简单的方法可以在 C 或 C++ 中使用它们(除非您在 C++ 中为具有模式,在这种情况下,您有一些环境表示,并且您需要一个标准的统一例程来进行匹配)。

        如果您需要 C++ 代码中的模式,我建议您制作一个 C++ 代码生成器,将您的模式 [以您的领域特定语言表达] 转换为 C++ 代码。

        我已经在GCC MELT 中为 C(使用模式变量将模式从 MELT 转换为 C)做了等效的工作,这是一种扩展 GCC 编译器的领域特定语言(带有匹配和模式)。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-06-16
          • 1970-01-01
          • 2020-08-16
          • 2011-03-12
          • 2014-05-03
          相关资源
          最近更新 更多