下一页 上一页 目录

7. 调试

尤其是在学习时,拥有调试功能非常重要。幸运的是,YACC 可以提供很多反馈。这种反馈会带来一些开销,因此您需要提供一些开关来启用它。

在编译语法时,将 --debug 和 --verbose 添加到 YACC 命令行。在您的语法 C 头文件中,添加以下内容

int yydebug=1;

这将生成文件 'y.output',其中解释了创建的状态机。

当您现在运行生成的二进制文件时,它将输出 *大量* 正在发生的事情。 这包括状态机当前所处的状态,以及正在读取的标记。

Peter Jinks 撰写了一个关于 调试 的页面,其中包含一些常见错误以及如何解决它们。

7.1 状态机

在内部,您的 YACC 解析器运行一个所谓的“状态机”。顾名思义,这是一个可以处于多种状态的机器。然后有一些规则来管理从一个状态到另一个状态的转换。一切都从我之前提到的所谓“根”规则开始。

引用示例 7 y.output 的输出

state 0

    ZONETOK     , and go to state 1

    $default    reduce using rule 1 (commands)

    commands    go to state 29
    command     go to state 2
    zone_set    go to state 3

默认情况下,此状态使用 'commands' 规则进行归约。这是前面提到的递归规则,它定义了 'commands' 由单个命令语句构建,后跟一个分号,然后可能还有更多命令。

此状态会一直归约,直到遇到它能理解的东西,在本例中是 ZONETOK,即单词 'zone'。然后它进入状态 1,该状态进一步处理 zone 命令。

state 1

    zone_set  ->  ZONETOK . quotedname zonecontent   (rule 4)

    QUOTE       , and go to state 4

    quotedname  go to state 5

第一行中有一个 '.' 来指示我们所处的位置:我们刚刚看到了 ZONETOK,现在正在寻找 'quotedname'。显然,quotedname 以 QUOTE 开头,这会将我们发送到状态 4。

要进一步了解这一点,请使用调试部分中提到的标志编译示例 7。

7.2 冲突:'shift/reduce','reduce/reduce'

每当 YACC 警告您存在冲突时,您可能会遇到麻烦。解决这些冲突似乎有点像一门艺术,它可能会让您学到很多关于您的语言的知识。可能比您想知道的还要多。

问题围绕如何解释标记序列展开。假设我们定义一种需要接受以下两个命令的语言

        delete heater all
        delete heater number1

为此,我们定义以下语法

        delete_heaters:
                TOKDELETE TOKHEATER mode
                {
                        deleteheaters($3);
                }
        
        mode:   WORD

        delete_a_heater:
                TOKDELETE TOKHEATER WORD
                {
                        delete($3);
                }

您可能已经预感到麻烦了。状态机首先读取单词 'delete',然后需要根据下一个标记决定去哪里。下一个标记可以是模式,指定如何删除加热器,也可以是要删除的加热器的名称。

然而,问题在于对于这两个命令,下一个标记都将是 WORD。因此,YACC 不知道该怎么做。这会导致 'reduce/reduce' 警告,以及另一个警告,即 'delete_a_heater' 节点永远不会被访问到。

在这种情况下,冲突很容易解决(例如,通过将第一个命令重命名为 'delete heaters all',或者通过将 'all' 作为单独的标记),但有时会更难。当您将 --verbose 标志传递给 yacc 时生成的 y.output 文件可能会有很大帮助。


下一页 上一页 目录