将FIRST和FOLLOW集编码为递归下降解析器

时间:2012-03-18 18:56:32

标签: parsing data-structures compiler-construction recursive-descent

这是我问过How to encode FIRST & FOLLOW sets inside a compiler的上一个问题的后续问题,但这个问题更多的是关于我的程序的设计。

我正在通过编写递归下降解析器来实现编译器的语法分析阶段。我需要能够利用FIRST和FOLLOW集,这样我就能更有效地处理源程序语法中的错误。我已经为我的所有非终端计算了FIRST和FOLLOW,但我在确定将它们逻辑放置在我的程序中的位置以及最佳数据结构将会有什么问题。

注意:所有代码都是伪代码

选项1)使用地图,并按名称将所有非终端映射到包含FIRST和FOLLOW集的两个集:

class ParseConstants
    Map firstAndFollowMap = #create a map .....
    firstAndFollowMap.put("<program>", FIRST_SET, FOLLOW_SET)
end

这似乎是一个可行的选项,但在我的解析器中,我需要像这样排序丑陋的代码来检索FIRST和FOLLOW并传递给错误函数:

#processes the <program> non-terminal
def program
    List list    = firstAndFollowMap.get("<program>")
    Set FIRST    = list.get(0)
    Set FOLLOW   = list.get(1)  

   error(current_symbol, FOLLOW)
end

选项2)为每个非终端创建一个类,并具有FIRST和FOLLOW属性:

class Program
    FIRST    = .....
    FOLLOW = .... 
end

这导致代码看起来更好一些:

#processes the <program> non-terminal
def program
   error(current_symbol, Program.FOLLOW)
end

这是我想到的两个选项,我很想听到关于如何编码这两组的任何其他建议,而且我发布的两种方式的任何批评和补充都会有所帮助。 感谢

我也在这里发布了这个问题:http://www.coderanch.com/t/570697/java/java/Encode-FIRST-FOLLOW-sets-recursive

1 个答案:

答案 0 :(得分:2)

你真的不需要FIRST和FOLLOW套装。您需要计算那些以获取解析表。这是一个{<non-terminal, token> -> <action, rule>}表,如果LL(k)(这意味着在堆栈中看到non-terminal并在输入中看到tokenaction要采用rule,如果适用,{ {1}}要申请),或{<state, token> -> <action, state>} if(C | LA |)LR(k)表(表示堆栈中给定state和输入中token表{ {1}}获取并转到action

获得此表后,您不再需要FIRST和FOLLOWS了。


如果您正在编写语义分析器,则必须假设解析器正常工作。短语级别错误处理(这意味着处理解析错误)与语义分析完全正交。

这意味着在解析错误的情况下,短语级别错误处理程序(PLEH)将尝试修复错误。如果不能,则解析停止。如果可以,语义分析器不应该知道是否存在修复的错误,或者根本没有任何错误!

您可以查看my compiler library示例。


关于短语级错误处理,您再次不需要FIRST和FOLLOW。我们现在谈谈LL(k)(仅仅因为关于LR(k)我还没有考虑过多少)。在构建语法表之后,你有很多条目,就像我说的那样:

state

现在,当您解析时,您可以获取堆栈中的任何内容,如果它是终端,则必须将其与输入匹配。如果它不匹配,短语级别错误处理程序将启动:

角色一:处理丢失的终端 - 只需在词法分析器中生成所需类型的假终端并让解析器重试。你也可以做其他事情(例如在输入中提前检查,如果你有你想要的令牌,从词法分析器中删除一个令牌)

如果您从堆栈中得到的是非终端(<non-terminal, token> -> <action, rule> ),则必须查看词法分析器,获取T并查看您的表格。如果条目lookahead存在,那么你很高兴。按照操作并从堆栈中推送/弹出。但是,如果不再存在这样的条目,那么短语级别错误处理程序将启动:

角色二:处理意外的终端 - 你可以做很多事情来传递这个。你做什么取决于<T, lookahead>T是什么以及你对语法的专业知识。

您可以做的事情包括:

  • 失败!你无能为力
  • 忽略此终端。这意味着您将lookahead推送到堆栈(再次推回lookahead之后)并让解析器继续。解析器首先匹配T,然后将其丢弃并继续。示例:如果您有这样的表达式:lookahead,则可以通过这种方式删除意外*1+2/0.5
  • *更改为可接受的内容,将lookahead推回并重试。例如,像这样的表达式:T可能是非法的,因为您不接受以数字开头的ID。您可以将其替换为5id = 10;,例如继续