AST的树模式匹配.c++中任何干净的方式

Tree-pattern matching of AST. Any clean way in C++?

本文关键字:任何干 方式 c++ 模式匹配 AST      更新时间:2023-10-16

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

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

假设我有一个抽象类(例如。有纯虚函数),lvaluelvalue仅分为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的混乱?请建议:)

Yuriy Solodkyy目前正在开发一个名为Mach7的c++ 11库,该库提供了一个类似开关的语句来完成此操作。他关于类型切换和模式匹配的幻灯片和论文(与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对Skeen在c++中进行ml风格模式匹配的相关问题的回答。

这可能取决于您的实际上下文…我相信你真正的代码会做一些比

更有趣的事情
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(); }
};

c++不是一种很好的语言,不适合用这种表面语法模式进行模式匹配;最好的情况是,你必须编写复杂的访问器,并进行各种检查,以了解AST的结构。

您可能会考虑使用一个专为AST转换而设计的工具,例如,"源到源程序转换"工具。其中大多数将允许您编写某种用于启用重写的表面语法模式,即"如果您看到此语法,则将其替换为该语法"形式的规则。

现在,大多数这样的工具都要求您以某种方式向工具定义您想要操作的语言,以便它知道该语法是什么。你没有说你想操纵哪一种语言;你的SO c++标签似乎在那里,因为你想在c++中这样做。如果你想操纵c++,我不会感到惊讶,因为这是你正在编码的,但这只是一个猜测。但是如果您打算使用一个程序转换工具,您需要一个健壮的语言定义,而这些很难获得。为了这个目的,很难得到一个好的c++定义,尤其是现在c++ 11已经出来了。

我们的DMS软件再造工具包与它的c++前端将能够为c++做到这一点。DMS提供了对AST的解析,对AST的过程访问(就像您在c++程序中所做的那样),但更重要的是,提供了源模式的概念。

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

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

语言和方言由声明指定。在本例中,语言是c++(这里明显拼写为Cpp),方言是gcc4 (DMS可以处理各种c++方言)。

模式被命名为(因为我们经常有很多模式和规则)*numeric_literal_index*。该模式由标识符(这是一个c++语法标记)和numeric_literal(类似地,但这是一个语法非终结符,允许无数c++数字文字类型中的任何一种)参数化,因为我们想要匹配1,3l等。该模式被限制为匹配lhs (c++语法中的非终结符),尽管在实践中,由于其语法,它不会匹配任何其他内容。实际的模式包含在metaquotes"…将c++特有的语法从模式语言的语法海洋中隔离出来。

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

当然,可以编写更复杂的模式,甚至使用它重写规则。一个教训:在工作中使用正确的工具。

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

如果你需要c++代码中的模式,我建议制作一个c++代码生成器,它可以将你的模式(用你的领域特定语言表达)转换成c++代码。

我已经在GCC MELT(一种特定于领域的语言)中为C做了等效的工作(将模式,带模式变量,从MELT转换为C)。