4

我在 bison 中创建了一个上下文无关语法,在 flex 中创建了一个扫描仪。现在我还想做一个语义检查,例如,假设输入是这样的:

int m=5;
c=c+5;

此输入在语法上是正确的,但使用了一个未声明的变量,即“c”。我怎样才能做这样的语义检查?我应该从哪里开始?我应该用 flex 还是 bison 编写代码?如果有人可以提供帮助,我将不胜感激。谢谢。

4

1 回答 1

8

首先要考虑的是:什么时候我们有足够的信息来进行语义检查?

对于像 C 这样的静态语言,我们可以在解析时使用语法指导规则(例如在 Yacc 中触发的规则)来执行此语义。

您的解析器需要维护符号表。也就是说,每当您打开一个新的范围,例如新的函数体或语句块时,您必须为该范围创建一个新的符号表对象(并在某个全局解析器变量中保留一个指向该对象的指针作为“当前范围” )。作用域还有一个指向前一个作用域的指针。当范围关闭时,您将原始范围恢复为“当前范围”。此范围的打开和关闭与处理块构造(如函数或语句体或结构体)的解析器规则相关联。

范围包含变量名称和语义信息之间的关联,例如它是什么类型的符号,以及其他属性(例如类型)。

当您的解析器处理某种声明时,声明的名称被引入到当前符号表中,然后它就被知道了。

所以,快进到我们的问题:如何检查名称是否未定义。这并不难。在某处,您的解析器具有如下规则

primary_expression : '(' expression ')'
                   /* ...*/
                   | CONSTANT
                   | IDENT
                   ;

主表达式可以是标识符,例如变量、常量或函数名称。如果规则很严格,如果可以使用就必须定义这些规则,我们可以在这里进行检查。

对于 的动作规则IDENT,我们在当前符号表中查找标识符。如果搜索结果一无所获,我们会引发一个错误,即存在未定义的标识符。

伪代码:

primary_expression : '(' expression ')'
                   /* ...*/
                   | CONSTANT
                   | IDENT {
                       struct symbol *sym = symbol_lookup(current_scope, $1);
                       if (sym == NULL) {
                         static_error("undeclared identifier %s", $1);
                         $$ = error_node();
                       } else {
                         /* ... */
                       }
                     }

symbol_lookup函数不仅查看当前范围!如果在当前作用域中找不到标识符,它会递归到父作用域,依此类推。范围链中的顶级范围是文件范围。如果在那里找到标识符,则它是某种全局标识符。如果在那里也没有找到它,它是未定义的。我也编了static_error;它具有printf类似的参数,并添加文件/行号信息,并增加错误计数(这样当解析器完成时,它可以根据错误计数非零来指示失败)。我error_node也编了;它是一个函数或宏,它产生某种指示错误的节点(可能只是一个空指针)。您的解析器规则必须产生一些东西并将其存储到$$. 对于不存在的标识符,我们可以将一些标记放入树中。

如果您使用 Yacc 在 C 中编写编译器,那么您需要做很多工作来发明所有这些数据结构(如符号表)并编写支持库。

于 2013-06-08T01:31:28.173 回答