C++ 手写递归上升解析器中的递归左递归

C++ 手写递归上升解析器中的递归左递归,c++,parsing,c++11,C++,Parsing,C++11,我一直在写一些递归上升解析器,我一直在努力解决的问题之一是左递归。对我来说很明显,正确的递归可以递归地表达,比如 addExpr : primaryExpr '+' addExpr | primaryExpr; 通过类似于 parseAddExpr() { auto x = parsePrimaryExpr(); if (next_token == '+') { auto result = make_unique<PlusExpr>(

我一直在写一些递归上升解析器,我一直在努力解决的问题之一是左递归。对我来说很明显,正确的递归可以递归地表达,比如

addExpr
    : primaryExpr '+' addExpr
    | primaryExpr;
通过类似于

parseAddExpr() {
    auto x = parsePrimaryExpr();
    if (next_token == '+') {
        auto result = make_unique<PlusExpr>();
        result->lhs = x;
        result->rhs = parseAddExpr();
        return std::move(result);
    }
    return std::move(x);
}
parseAddExpr(){
auto x=parsePrimaryExpr();
如果(下一个_标记=='+'){
自动结果=使_唯一();
结果->lhs=x;
结果->rhs=parseAddExpr();
返回std::移动(结果);
}
返回std::move(x);
}
但对于左递归,我能想到的只是一个while循环

mulExpr
    : mulExpr '*' addExpr
    | addExpr;

parseMulExpr() {
    auto expr = parseAddExpr();
    while(next_token == '*') {
        auto mul_expr = make_unique<MulExpr>();
        mul_expr->lhs = std::move(expr);
        mul_expr->rhs = parseAddExpr();
        expr = std::move(mul_expr);
    }
    return std::move(expr);
}
mulExpr
:mulExpr'*'addExpr
|加法器;
parseMulExpr(){
auto expr=parseAddExpr();
while(下一个_标记=='*')){
auto mul_expr=使_唯一();
mul_expr->lhs=std::move(expr);
mul_expr->rhs=parseAddExpr();
expr=std::move(mul_expr);
}
返回std::move(expr);
}

我的意思是,这个函数很好,但我总是觉得它应该有一个递归版本。是否可以以递归而不是迭代的方式实现左关联运算符?

您的函数是递归下降,而不是递归上升。递归下降解析器在左递归中遇到的问题是众所周知的,并且已经得到了研究。任何涉及语法分析的编译器课程或教科书都将讨论这个问题以及解决它的方法

使用迭代是一种非常正常、有效的处理方法。例如在这些注释中,规则
T->T'*'S | T'/'S | S
(这是添加了除法的
mulExpr
规则)被转换为规则
T->S{('*'|'/')S}
,其中大括号
{…}
表示“零次或多次重复”

更新 根据你的评论,我认为你对“递归下降”和“递归上升”的含义有些困惑

递归下降 递归下降的基本思想是为语法中的每个非终结符创建一个函数。与一些非终结符A对应的函数的工作是尽可能完整地解析A的一个实例,必要时调用语法中出现在A产品右侧的非终结符函数

例如,您的语法有一个非终结符
addExpr
,包含以下两个结果:

addExpr -> primaryExpr '+' addExpr
addExpr -> primaryExpr
因此,递归下降解析器将具有一个用于
addExpr
的函数,该函数尝试匹配
addExpr
产品之一的右侧,根据需要调用
primaryExpr
addExpr
(自身!)的函数,因为这些非终结符出现在
addExpr
的产品中

实际上,这正是您在
parseAddExpr
函数中所拥有的。它寻找一种匹配
addExpr
产品之一的方法,根据需要调用
parsePrimaryExpr
parseAddExpr

递归上升 递归上升是实现LR解析的一种(非常少见的)方法。LR解析器有一个状态表,每个状态有一行,每个终端有一列。在递归上升解析器中,我们不将表表示为数据。相反,我们为每个状态创建一个函数,该状态的行在函数中体现为switch语句

在LR解析器中,通常在状态和非终结符之间,或者在状态和结果之间存在一对一的对应关系。一般来说,州会比产品多。每个状态表示产品中的一组位置

您的解析器 查看您的帖子中的函数,我看不到任何证据表明您构造或实现了状态表。我看到的是一组函数,它们与语法中的非终结符直接对应。这种对应关系是递归下降的标志


此外,您在使用左递归产品时遇到麻烦的事实是您正在使用递归下降。LR解析器在左递归和递归下降解析器方面没有问题。

这看起来像是一个“宽”问题:-)不是真的。迭代版本工作得很好,我已经用了一段时间了。事实上,就是那个几天前向我寻求解析帮助的家伙让我重新思考了一下,你们当然可以把迭代转换成递归。但你为什么要这么做。迭代通常更安全,正如您所说,它解决了这个问题。在我看来,从左到右进行词汇分析的事实确实在左递归实现和右递归实现之间引入了一些基本的不对称性。你需要某种临时的深度存储来强制左手边是一个递归辅助调用……怎么可能为左关联运算符计算出左递归呢?我不知道我的代码是如何递归下降的。我的意思是,如果你看一下
addExpr
,那么它会在做出决定后移动一个
primExpr
,然后移动另一个
addExpr
,然后减少规则。