我在 bison 中创建了一个上下文无关语法,在 flex 中创建了一个扫描仪。现在我还想做一个语义检查,例如,假设输入是这样的:
int m=5;
c=c+5;
此输入在语法上是正确的,但使用了一个未声明的变量,即“c”。我怎样才能做这样的语义检查?我应该从哪里开始?我应该用 flex 还是 bison 编写代码?如果有人可以提供帮助,我将不胜感激。谢谢。
我在 bison 中创建了一个上下文无关语法,在 flex 中创建了一个扫描仪。现在我还想做一个语义检查,例如,假设输入是这样的:
int m=5;
c=c+5;
此输入在语法上是正确的,但使用了一个未声明的变量,即“c”。我怎样才能做这样的语义检查?我应该从哪里开始?我应该用 flex 还是 bison 编写代码?如果有人可以提供帮助,我将不胜感激。谢谢。
首先要考虑的是:什么时候我们有足够的信息来进行语义检查?
对于像 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 中编写编译器,那么您需要做很多工作来发明所有这些数据结构(如符号表)并编写支持库。