AST的树型匹配。在C ++中任何干净的方式?

时间:2011-10-28 17:20:56

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

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

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

考虑我有一个抽象类(即具有纯虚函数),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的混乱? 请告知:)

4 个答案:

答案 0 :(得分:3)

Yuriy Solodkyy目前正在开发一个名为Mach7的C ++ 11库,它提供类似于开关的语句来执行此操作。他的slides以及type 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样式的模式匹配。

答案 1 :(得分:1)

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

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

答案 2 :(得分:1)

C ++不是用这种表面语法模式进行模式匹配的好语言;在最好的情况下,你必须编写复杂的访问者,通过各种检查来了解AST的结构。

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

现在,大多数此类工具都要求您以某种方式定义要操作该工具的语言,因此它知道该语法是什么。你没有说你想操纵哪种语言;你的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] ";

语言和方言由声明指定。在这种情况下,语言是C ++(这里明显拼写为Cpp),方言是gcc4(DMS可以处理各种C ++方言)。

模式被命名(因为我们经常有许多模式和规则)* numeric_literal_index *。模式由标识符(这是一个C ++语法标记)和numeric_literal(同样,但这是一个允许任何数十亿C ++数字文字类型的语法非终结符号)参数化,因为我们想匹配1,3L等。 该模式被约束为匹配 lhs (C ++语法非终结符号),但实际上由于其语法,这将不匹配任何其他内容。实际模式包含在 metaquotes “...”中,它将C ++特定语法与模式语言语法的环绕声隔离开来。

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

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

答案 3 :(得分:0)

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

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

我已经完成了GCC MELT中C语言的转换(使用模式变量,从MELT到C),这是一种特定于域的语言(带有匹配和模式),以扩展GCC编译器。