如何在胜利野牛中使用非平凡的YYSTYPE

时间:2017-02-25 07:12:22

标签: c++ compilation bison yacc

起初我使用野牛的LR解析器,现在由于某些语法难以处理,我决定抓住野牛的GLR解析器。

目前我将YYSTYPE定义为一个非平凡的类ParseNode,但我在野牛的生成代码中找到了

union yyGLRStackItem {
  yyGLRState yystate;
  yySemanticOption yyoption; // my YYSTYPE is located in the struct
};

我的非平凡类包含在union中,因此整个代码不会编译。

通过搜索,我发现%define api.value.type可以帮助我。

但不管我怎么试,输出总是显示

error: %define variable 'api.value.type' is not used

我的win_bison版本是win_bison 2.4.8 / 2.5.8。也许这个版本不支持bison 3.0?如果是这样,我还能做什么?

修改

在我使用bison的LR解析器之前,我使用了我的ParseNode,虽然堆栈无法自动增长,但在切换到GLR解析器之前它实际上已经工作了。 我将ParseNode定义如下:

struct ParseNode {
    FlexState fs; // all infomation I got from flex
    std::vector<ParseNode *> child;
    struct ParseNode * father;
    struct ParseAttr * attr = nullptr;
    void addchild(const ParseNode & n);
    ParseNode & get(int child_index);
    const ParseNode & get(int child_index) const;
    void setattr(ParseAttr * pa);
    std::string to_string() const { return fs.CurrentTerm.what; }

    ParseNode(const ParseNode &);
    ParseNode & operator= (const ParseNode &) ;
    ParseNode() = default;
    ParseNode(const FlexState & s, ParseNode * fa, ParseAttr * att = nullptr) : father(fa), attr(att), fs(s) {}
    ~ParseNode();
};

编辑2

这是我对ParseNode

的实现
ParseNode::~ParseNode()
{
    delete attr;
    for (int i = 0; i < child.size(); i++)
    {
        delete child[i];
    }
}
ParseNode::ParseNode(const ParseNode & pn)
{ 
    this->fs = pn.fs;
    this->father = pn.father;
    this->attr = (pn.attr == nullptr ? nullptr: pn.attr->clone());
    for (int i = 0; i < pn.child.size(); i++)
    {
        if (pn.child[i] != nullptr) {
            this->addchild(pn.get(i));
        }
        else {
            this->addchildptr(nullptr);
        }
    }
}
ParseNode & ParseNode::operator= (const ParseNode & pn) {
    if (this == &pn) {
        return *this;
    }
    else {
        delete this->attr;
        for (int i = 0; i < child.size(); i++)
        {
            delete child[i];
        }
        this->child.clear();

        this->fs = pn.fs;
        this->father = pn.father;
        this->attr = (pn.attr == nullptr ? nullptr : pn.attr->clone());
        for (int i = 0; i < pn.child.size(); i++)
        {
            this->addchild(pn.get(i));
        }

        return *this;
    }
}
void ParseNode::addchildptr(ParseNode * ptrn, bool add_back) {
    if (ptrn != nullptr) {
        ptrn->father = this;
    }
    if (add_back) {
        this->child.push_back(ptrn);
    }
    else {
        this->child.insert(this->child.begin(), ptrn);
    }
}
void ParseNode::addchild(const ParseNode & n, bool add_back ) {
    this->addchildptr(new ParseNode(n), add_back);
}

我通过RAII方法实现我的ParseNode类,因为资源管理将由编译器自动处理。所以我可以写这样的代码:

/* more codes */
exp : exp '+' exp 
        {
            const ParseNode & exp1 = $1;
            const ParseNode & op = $2;
            const ParseNode & exp2 = $3;
            ParseNode newnode = gen_exp(exp1, op, exp2);
            newnode.addchild(exp1); // add a copy of left operand exp's ParseNode
            newnode.addchild(op); // op
            newnode.addchild(exp2); // add a copy of right operand exp's ParseNode
            $$ = newnode;
        }
    /* more codes */

gen_exp函数生成ParseNode之后,有许多gen_个函数,它们都接受const ParseNode &并返回ParseNode。我不必担心右边的三个ParseNode,因为当它的解构器被调用时,他拥有的所有指针(不包括father )将被删除。像father这样的weak_ptr指针,除了帮助我调试之外,还没有解析用法。一旦野牛部分代码完成,%start规则将返回完整的语法树。

如果我将ParseNode更改为ParseNode *,我必须做出哪些更改?我决定在每条规则的最后删除$1.pointer_to_parsenode$n.pointer_to_parsenode。是否足以避免内存泄漏,更重要的是,读取脏值或导致访问冲突?

1 个答案:

答案 0 :(得分:0)

通常,使用像ParseNode这样的非平凡数据类型的常规方法是使用指向这些对象的指针而不是对象本身作为语义类型。

正如其名称所示,解析堆栈是一个堆栈,随着解析的进行,元素将被推送并弹出。因此,创建一个指向堆栈上对象的引用或指针是危险的,因为稍后该堆栈槽可能被完全不同的对象占用。您的ParseNode对象包含以下指针:

std::vector<ParseNode *> child;
struct ParseNode * father;

如果没有看到用于维护这些指针的代码,则很难确定,但是在将ParseNode添加到child向量之前,似乎不太可能复制ParseNode;如果要插入此向量的节点的地址是堆栈槽,那么在执行期间的某个时刻肯定会调用未定义的行为。 (编辑:好的,我错了:在将整个树添加到child向量之前,实现确实会对整个树进行深层复制,从而使解析器执行时间呈二次方而不是输入大小为线性。)

如果您将语义类型设置为ParseNode*而不是ParseNode,则大多数问题都会消失,尽管您必须实施内存管理策略。 (这不是一个缺点,因为允许在从堆栈弹出对象时删除对象的内存管理策略根本不起作用。)在仅构建语法 tree 的简单应用程序中,问题并不严重,因为在完成根目录后,您可以通过树来删除节点。如果应用程序可以形成语法林,例如通过公共子表达式(CSE)优化,则内存管理变得更加困难。 (虽然一个彻底的垃圾收集器不是很难编码,但我通常的方法是分配std::deque或等效的所有节点,然后不用担心删除任何节点,直到不再需要整个语法图,这一点就足以删除后备存储。)

一旦你将语义类型更改为指针,使用非平凡类型的问题也会消失,因为指针是一个普通的类型,即使它指向的不是。

最终结果是在您的解析节点实现和客户端代码(不再需要担心仅使用对节点的引用以避免昂贵的副本)的大量简化,代价是使用{{ 1}}用于最终的AST根。